@DataJpaTest
@DataJpaTest is a slice for testing the persistence layer. It auto-configures Spring Data JPA, your repositories, an embedded database, and a TestEntityManager — but leaves out web and service beans. Each test runs in a transaction that is rolled back at the end, so tests never pollute each other or the database.
What the slice loads
Annotate the test and inject the repository under test plus a TestEntityManager.
@DataJpaTest
class ProductRepositoryTest {
@Autowired
ProductRepository repository;
@Autowired
TestEntityManager entityManager; // helper for arranging data
}
By default @DataJpaTest:
- Configures Hibernate, Spring Data, and the
DataSource. - Replaces your real database with an embedded one (H2/HSQLDB/Derby) if on the classpath.
- Wraps each test in a transaction and rolls it back afterward.
- Enables SQL logging so you can see the generated statements.
TestEntityManager
TestEntityManager is a test-friendly EntityManager. Use persist/persistAndFlush to arrange data without going through the repository you are testing, which keeps arrange and assert independent.
@Test
void findsByName() {
entityManager.persistAndFlush(new Product("Keyboard", new BigDecimal("49.90")));
entityManager.persistAndFlush(new Product("Mouse", new BigDecimal("19.90")));
List<Product> found = repository.findByName("Keyboard");
assertThat(found).hasSize(1);
assertThat(found.get(0).getPrice()).isEqualByComparingTo("49.90");
}
Tip:
persistAndFlushforces the INSERT immediately and clears the persistence-context cache for that entity, so a subsequent repository read genuinely hits the database rather than returning the cached instance.
Testing derived queries
Derived queries generate SQL from the method name. A @DataJpaTest is the perfect place to verify they return what you expect.
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByPriceGreaterThan(BigDecimal threshold);
Optional<Product> findFirstByNameOrderByPriceAsc(String name);
}
@Test
void filtersByPrice() {
entityManager.persist(new Product("Cheap", new BigDecimal("5.00")));
entityManager.persist(new Product("Pricey", new BigDecimal("99.00")));
entityManager.flush();
List<Product> expensive = repository.findByPriceGreaterThan(new BigDecimal("50.00"));
assertThat(expensive)
.extracting(Product::getName)
.containsExactly("Pricey");
}
Testing @Query methods
Custom JPQL or native queries deserve a test because the query string is not checked at compile time.
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("select p from Product p where p.price between :min and :max")
List<Product> inPriceRange(BigDecimal min, BigDecimal max);
}
@Test
void returnsProductsInRange() {
entityManager.persist(new Product("A", new BigDecimal("10")));
entityManager.persist(new Product("B", new BigDecimal("50")));
entityManager.persist(new Product("C", new BigDecimal("90")));
entityManager.flush();
List<Product> result = repository.inPriceRange(new BigDecimal("20"), new BigDecimal("70"));
assertThat(result).extracting(Product::getName).containsExactly("B");
}
Output:
Hibernate: select p1_0.id, p1_0.name, p1_0.price from product p1_0 where p1_0.price between ? and ?
ProductRepositoryTest > returnsProductsInRange() PASSED
Embedded vs real database
By default the slice swaps in an embedded database. That is fast, but H2 is not Postgres — dialect quirks, native queries, and DB-specific features can behave differently. To run against the real configured database, disable the replacement.
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class ProductRepositoryRealDbTest { }
Replace value | Effect |
|---|---|
ANY (default) | Always replace with an embedded DB |
AUTO_CONFIGURED | Replace only auto-configured (not explicitly defined) DataSources |
NONE | Use the real configured DataSource — no replacement |
Warning: Testing derived queries against H2 when production runs Postgres can give false confidence. For high-fidelity repository tests, set
replace = NONEand provide a real Postgres via Testcontainers with@ServiceConnection.
Verifying rollback behavior
Each @DataJpaTest method is transactional and rolls back, so data written in one test is invisible to the next. If you need to assert the committed state, flush and clear, then re-read:
@Test
void persistsWithGeneratedId() {
Product saved = repository.save(new Product("X", BigDecimal.ONE));
entityManager.flush();
entityManager.clear(); // drop first-level cache
Product reloaded = repository.findById(saved.getId()).orElseThrow();
assertThat(reloaded.getName()).isEqualTo("X");
}