Should I test that?

Should I test the repository layer?

Verdict

Yes

Test each query in the repository layer with an integration test against the database engine production runs, using rows that each filter must return and rows it must leave out; do not unit test repositories with a mocked database.

Why

Test every query in the repository layer, against the database engine production runs. The typical case is a backend where each repository holds the queries for one entity and a service calls them. Blast radius is users and Change frequency is regularly, because customers see the rows these queries return, and most features add a filter. Detectability is eventually, because a wrong condition returns plausible rows without an error. Reversibility is with-effort, because code acts on the wrong rows until someone repairs them, and Test cost is moderate, because each test needs a database in a container, so rule R11 gives Test.

When the decision changes
WhenDecisionWhy
The repository only inherits save, findById and delete from a framework base such as Spring Data CrudRepositoryDo not test the repository; test the code that calls itChange frequency falls to rarely, Detectability to immediately and Reversibility to trivial, because the framework's own suite covers these methods
Endpoint tests run each repository query against a real database, with rows that each filter must leave outDo not add separate repository testsDetectability moves to immediately and Reversibility to trivial, because a wrong filter fails those tests in CI
Every repository query adds the tenant ID, and several companies share one databaseTest mandatory: records of two tenants in each test, checking that each query returns one tenant's rowsBlast radius rises to safety-or-legal and Detectability to never, because a missing tenant filter leaks records without an error
A repository query selects the subscriptions that the nightly billing job chargesTest mandatory: the main path, every known failure and the date boundariesBlast radius rises to money and Reversibility to costly, because a double charge needs a refund
A read-only query feeds an internal staff dashboard, and no code acts on its rowsTest minimally: one database test of the main filterBlast radius falls to internal and Reversibility to trivial
The queries live in stored procedures that tests can reach only through a shared staging databaseTest it differently: a nightly job compares row counts with a reference query and alerts on a differenceTest cost rises to heavy while Detectability stays eventually and Reversibility with-effort

What breaks if you don't test

Repository bugs sit in the SQL, and wrong SQL raises no error. An Or in place of an And, or a join that repeats a row for each child record, returns a list that looks complete. Service tests mock the repository, so they stay green. A customer notices weeks later, when a record they know exists is missing from a list.

What you lose if you over-test

The common over-test mocks the EntityManager, DbSet or JDBC connection and asserts the query string. A mocked repository test passes when the SQL is wrong, because the SQL never runs, and fails when you rename a parameter. A test of the inherited save() and findById() checks Spring Data or EF Core instead of your code.

How to test

Write one integration test per query method, against the production database engine started with Testcontainers. Insert rows the query must return and rows it must leave out, including one exactly on each boundary, such as midnight at both ends of a date range. Assert the exact set of returned IDs, since a count passes when a wrong row replaces a right one. Spring Boot's @DataJpaTest test slice starts only the persistence layer; for .NET, follow the EF Core testing guidance.

When the answer changes

  • The repository declares no query of its own.
  • A query filters by tenant or owner.
  • A query selects the records that a job charges or pays out.

Real incident + Code example

The report that lost the last day of each month

On a property-management product I worked on, landlords received a monthly PDF of maintenance requests. The repository ran a native query with created_at BETWEEN :from AND :to, called with the first and last day of the month as LocalDate values. PostgreSQL treats a date as midnight when it compares one with a timestamp, so requests created on the last day after 00:00 were left out. The service test mocked the repository and passed. Four reports went out before a landlord asked why a burst pipe reported on 31 January was missing, and we regenerated them for about 120 landlords. The fix changed the upper bound to created_at < CAST(:to AS date) + 1, with this test:

@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
@Testcontainers
class MaintenanceRequestRepositoryTest {

    @Container @ServiceConnection
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");

    @Autowired MaintenanceRequestRepository requests;

    @Test
    void monthIncludesItsLastDayAndNothingOutsideIt() {
        requests.saveAndFlush(new MaintenanceRequest(7, LocalDateTime.parse("2026-02-28T23:59:00")));
        var first = requests.saveAndFlush(new MaintenanceRequest(7, LocalDateTime.parse("2026-03-01T00:00:00")));
        var last = requests.saveAndFlush(new MaintenanceRequest(7, LocalDateTime.parse("2026-03-31T18:30:00")));
        requests.saveAndFlush(new MaintenanceRequest(7, LocalDateTime.parse("2026-04-01T00:00:00")));

        var march = requests.findCreatedBetween(7, LocalDate.of(2026, 3, 1), LocalDate.of(2026, 3, 31));

        assertThat(march).extracting(MaintenanceRequest::getId)
            .containsExactlyInAnyOrder(first.getId(), last.getId());
    }
}

FAQ

Should I unit test repositories?

No, test repositories with integration tests against a real database instead of unit tests with a mocked EntityManager. A mocked database never runs the SQL, where a wrong filter or join sits.

Should DAOs be unit tested?

No, test a DAO with one integration test per query against the production database engine instead of a mocked JDBC connection. A test with a mocked connection repeats the SQL string and passes when the SQL is wrong.

Should I test Spring Data JPA repositories?

Yes, test each query method you declare in a Spring Data JPA repository, derived or written in @Query, with one @DataJpaTest against the production database engine. Skip the inherited save, findById and delete, which Spring Data's own suite covers.

Should repository tests use H2 or an in-memory database?

No, run repository tests on the database engine production runs, in a container. H2's documentation says its compatibility modes cover only a small subset of the differences between databases, so a query that passes on H2 can fail on PostgreSQL.