What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Put a behavior in an abstract service base class, and share its tests, only when every inheriting service really follows the same rule. In Chapter 10 of Kamen Ivanov’s Spring service-layer testing series, the shared test suite covers CRUD authorization, ownership stamping, not-found handling and idempotent delete. The changeStatus() methods in ProductsServiceImpl and CategoriesServiceImpl look alike, but the chapter keeps them, and their tests, in the concrete services because the status rules differ.
What belongs in the shared abstract suite
The chapter’s AbstractCrudServiceTestCase is a reusable test suite for create(), update(), loadById() and delete(). It asserts the rules every CRUD service in the family is expected to honor:
- Authorization guards reject callers who lack permission.
- Not-found guards fail predictably when an entity does not exist.
- Ownership is stamped on creation rather than trusted from the client.
- Deleting an already-deleted entity is idempotent.
Each concrete test class then adds what is specific to its domain: field mapping between entity and DTO, the Product specification branch, and its own status-change tests. The shared suite is therefore a floor, not a full description of any one service.
Where the shared behavior stops
The chapter’s test for a shared rule and its test for a domain rule are written in the same style, which is why the boundary is easy to blur. The table below sorts the behaviors discussed in the chapter by where they belong.
| Behavior | Where it lives | Reason given in the chapter |
|---|---|---|
| Authorization, not-found, ownership, idempotent delete | Abstract suite, reused by every CRUD service | The rule is the same for every service that inherits it. |
| Entity-to-DTO field mapping | Concrete test class | Each domain has its own fields. |
| Product specification create-or-update branch | Concrete ProductsServiceImpl tests |
Only products carry this specification. |
changeStatus() transitions |
Concrete services and their own tests | Product and Category do not share the same status rules. |
| Possible future event side effects of status changes | Concrete services, when they exist | The chapter anticipates Kafka-style side effects that may diverge; it does not report that such integrations are already in place. |
The reasoning is structural as well as practical. Not every domain object has a status field. Adding status hooks to a generic base service would either burden unrelated services with methods they never use or bake assumptions about one domain into a class that should describe only what all domains share.
Fixtures must exercise the branch they claim to protect
The chapter’s clearest example is the Product specification. The update logic has two paths:
- The product has no specification yet, so the service creates one.
- The product already has a specification, so the service mutates its dimensions and weight in place.
The author notes that the earlier update fixtures all omitted a specification. Those tests passed, and they exercised the first path, but they never established the second. Nothing in them proved that an existing specification was updated rather than replaced or ignored.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsThe lesson is about evidence, not metrics. Coverage tooling can tell you that a line or branch executed. It cannot tell you whether the test data and assertions proved the behavior that branch exists to protect. As Ivanov puts it: “Coverage tooling can tell you that a line or branch executed. It cannot tell you whether the test data and assertions proved the behavior that branch exists to protect.”
A practical check for any branch: write down the state the test must start from, then confirm the fixture actually creates that state. If the setup data has no specification, a test named for updating a specification is misnamed.
Keep test setup independent of other production behavior
An earlier helper, createPersistedEntity, prepared entities for other tests by calling the service’s own create() method, then cleared the DAO mock’s recorded invocations. That coupled tests for update(), delete() and loadById() to the behavior of create(). A bug or change in creation could fail tests whose subject was never creation.
Rank #4
The replacement builds a persisted fixture directly through a concrete helper, so each test establishes the state it needs without going through another production method. The payoff is diagnostic clarity: a test should fail for the behavior its name and assertions describe, and nothing else.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallWhat mocked-DAO tests cannot prove
A mocked-DAO unit test can show which calls a method makes, in what order, and under which conditions. It cannot show that the surrounding transaction boundary is applied, because the Spring AOP proxy that applies @Transactional is not part of that setup. In Ivanov’s words: “A mocked-DAO test verifies what a method does which calls happen, in what order, under what conditions, but the transactional boundary around those calls is applied by a Spring AOP proxy that never exists in this test setup at all.”
Best Value
The chapter does not treat this as a flaw in mocked testing. It is a boundary of what that layer can prove. The author’s conclusion is that catching a missing or misplaced @Transactional annotation requires an integration test that starts a real Spring context. Ivanov frames the broader point this way: “100% service-layer coverage” from unit tests alone was never 100% of what could go wrong.
A checklist for placing a new service behavior
- Does every service that inherits this behavior need the same rule? If not, keep it concrete.
- Would putting it in the base class add hooks that unrelated services must stub or ignore? If yes, keep it concrete.
- Does the test fixture establish the exact state the branch requires?
- Does the test set up its state directly, or by calling the method under test or another production method?
- Does the behavior depend on the Spring proxy, such as a transaction boundary? If so, add an integration test with a real Spring context.
Project prerequisites and publication details
The chapter points to the Git-tagged reference repository advanced-spring-multimodule at tag chapter-10-bl-testing. It states that Maven 3.9.x and Java 25 are required. These are the author’s stated prerequisites; they were not independently checked against the repository’s current state, so confirm the tag and build settings before relying on them.
The chapter appears under the same title on Kamen’s Substack and as a DEV Community repost by Kamen Ivanov, dated September 21, 2026, which states it was originally published on the Substack.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Ivanov’s chapter is written for Java and Spring developers deciding what an abstract CRUD service should contain and how to split shared tests from domain tests. Its reasoning transfers to any codebase with a similar base-service pattern.
Quick Recap
“
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

