Verdict
No
Do not write a unit test for a method that only forwards one value, or values of different types, to another method; test the code that calls it.
Why
- Blast radiususers
- Change frequencyrarely
- Detectabilityimmediately
- Reversibilitytrivial
- Test costtrivial
Do not write a unit test for a method that only calls another method and returns the result; test the code that calls it. The typical case is a service method findById(id) that returns repository.findById(id), and controller or integration tests run it in CI. Blast radius is users, because a wrong result reaches a screen, and Change frequency is rarely, because a pass-through changes when its delegate is renamed. Detectability is immediately and Reversibility is trivial: a wrong call fails to compile or fails the callers' tests before merge, and a revert leaves no data behind. Test cost is trivial, and rule R8 gives Do not test.
| When | Decision | Why |
|---|---|---|
| The pass-through forwards several arguments of one type, such as a start date and an end date | Test: one test that passes a distinct value for each argument and checks where each one lands | Detectability moves to eventually: a swapped argument produces a plausible result of the right type |
| The delegate has a sibling with the same signature, such as `findAll()` next to `findAllByActiveTrue()` | Test: one test with data that only the right method leaves out | Detectability moves to eventually and Reversibility to with-effort: the wrong call returns plausible rows that code acts on, and callers' tests pass when their data cannot tell the two methods apart |
| No test runs the code that calls the pass-through | Test minimally: one test of the main path, on the caller or through the pass-through | Detectability moves to same-day: the first report of a wrong result comes from a user |
| The pass-through forwards an amount and a currency to a payment client | Test mandatory: assert the amount and the currency that a fake payment client receives | Blast radius rises to money and Reversibility to costly, because a wrong charge ends in refunds |
| The pass-through carries an access annotation, such as Spring's `@PreAuthorize` | Test mandatory: one allowed call and one denied call through the security context | Blast radius rises to safety-or-legal and Detectability to never: without the annotation, every user can call the method and nothing fails |
| The pass-through deletes records, such as `close(id)` that calls `repository.deleteById(id)`, and no caller test checks that the other records remain | Test: one test against a test database that deletes one record and checks that the others remain | Detectability moves to eventually and Reversibility rises to costly: a caller test with one stored record passes when every record is deleted, and deleted records come back only from a backup, by hand |
What breaks if you don't test
When the callers' tests run the pass-through, nothing breaks that CI misses. The failures that get through are calls that compile and return a plausible result: findBetween(start, end) that calls findByDateBetween(end, start), or findActive() that calls findAll() and lists closed accounts. A caller test catches them only when its data tells the two cases apart; otherwise a support agent reports the wrong list days later.
What you lose if you over-test
The usual pass-through test mocks the delegate and checks verify(repository).findById(42). That test repeats the method body: it fails only on an intended change, and a test written from a wrong body copies the mistake. In a service with 30 pass-throughs, one repository rename becomes 30 test edits, and no test checks which rows the database returns.
What to do instead
Test the behaviour one level up: an HTTP-level test of the endpoint, or an integration test of the use case, runs the pass-through with the code that decides the result. Give the delegate its own tests where it holds logic, such as a custom query, against a test database; the Spring Boot testing reference covers web and database test slices. Test an access annotation on a pass-through with the Spring Security method test support.
When the answer changes
- The pass-through forwards two or more arguments of one type, such as two account IDs.
- The delegate has a sibling with the same signature, such as
findAll()andfindAllByActiveTrue(). - An annotation on the pass-through adds an access check, a transaction, or a cache.
Code example + Counterexample
The pass-through that listed closed accounts
Skipping fails when the delegate has a sibling with the same signature. The compiler accepts either call, a mock test copies whichever call the body makes, and only a test whose data tells the two apart fails:
List<Customer> findActive() {
return customers.findAll(); // bug: should call findAllByActiveTrue()
}
// Passes: the mock repeats the line it checks
when(customers.findAll()).thenReturn(List.of(ana));
assertEquals(List.of(ana), service.findActive());
// Fails: the test database holds a closed account
@Test
void findActiveLeavesOutClosedAccounts() {
customers.save(new Customer("ana", true));
customers.save(new Customer("bo", false));
assertEquals(List.of("ana"), names(service.findActive()));
}
A closed account in a customer list looks like ordinary data, so Detectability moves to eventually. A job that updates the listed customers also changes closed accounts, which someone fixes by hand, so Reversibility moves to with-effort and the framework gives Test for findActive().
Sources
Related questions
FAQ
- Do I need to write a unit test for a method that only calls a repository method?
No, a service method that only forwards to a repository method needs no unit test when a controller or integration test runs it. Test the repository method itself when it holds a custom query, against a test database.
- Do I need to write unit tests for methods of services without logic?
No, the tests of the calling code cover a service method without logic. Add a test when the method forwards several arguments of one type, or when its delegate has a sibling with the same signature.
- Should I use Mockito verify to test a method that only delegates?
No, a
verify()check on a pass-through repeats the method body, so a test written from a wrong body passes. Use a test whose data the wrong delegate would handle differently, the state verification that Martin Fowler's article on mocks and stubs describes.