Repository tests: @DataJpaTest
Name-based queries and @Query are easy to break with a typo in a condition — the application starts but finds the wrong things. They are worth testing separately. There is no need to start the whole application with its web part and security for that. The @DataJpaTest annotation creates a slice — only what is needed for database work: entities, repositories, Flyway and transactions.
A repository test
// src/test/java/com/example/blog/PostRepositoryTests.java
package com.example.blog;
import static org.assertj.core.api.Assertions.assertThat;
import java.time.Instant;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest;
@DataJpaTest
class PostRepositoryTests {
@Autowired
private PostRepository posts;
@Autowired
private UserRepository users;
@Test
void findsPostsByTitle() {
User anna = users.save(new User("anna", "{noop}changeme"));
posts.save(new Post("Hello, Spring", "Text", anna, Instant.now()));
posts.save(new Post("Java records", "Text", anna, Instant.now()));
List<Post> found = posts.findByTitleContainingIgnoreCase("spring");
assertThat(found).extracting(Post::getTitle).containsExactly("Hello, Spring");
}
@Test
void searchesTitleAndBody() {
User oleg = users.save(new User("oleg", "{noop}changeme"));
posts.save(new Post("Spring Data JPA", "Text", oleg, Instant.now()));
assertThat(posts.search("data")).hasSize(1);
assertThat(posts.search("TEXT")).hasSize(1);
assertThat(posts.findByAuthorUsernameOrderByCreatedAtDesc("oleg")).hasSize(1);
assertThat(posts.findLatest(5)).hasSize(1);
}
}extracting(Post::getTitle) takes the titles of the found posts, and containsExactly checks that exactly one — the right one — was found. The second test checks the case-insensitive JPQL search, the query through the author relationship and the plain SQL with limit.
What @DataJpaTest does
| Property | Value |
|---|---|
| Beans created | Entities, repositories, DataSource, Flyway |
| Beans not created | Controllers, services, security |
| Database | An embedded in-memory one instead of the one in application.yaml |
| Transactions | Each test is rolled back automatically |
The log shows the database swap:
INFO ... Replacing 'dataSource' DataSource bean with embedded version
INFO ... Starting embedded database: url='jdbc:h2:mem:8fea3dcb-8214-4168-a703-9500904c0505;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'No test profile is needed here: the database is fresh for every run anyway. Flyway migrations are applied to it just as to the real one, so the test also checks the migration SQL.
The result
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.094 s -- in com.example.blog.PostRepositoryTestsThe slice starts in just over a second — noticeably faster than a full @SpringBootTest.
Which test to choose
| What we check | Annotation |
|---|---|
| Repository queries | @DataJpaTest |
| Addresses, response codes, security | @SpringBootTest + @AutoConfigureMockMvc |
| Service logic together with the database | @SpringBootTest |
| A class without Spring | Plain JUnit without Spring annotations |
Common mistake
If you put @EnableCaching or @EnableScheduling on the main class with @SpringBootApplication, the slice tries to enable them too, but the needed beans are not in it:
No qualifying bean of type 'org.springframework.cache.CacheManager' available: no CacheResolver specified - register a CacheManager bean or remove the @EnableCaching annotation from your configuration.Keep such annotations in a separate @Configuration class, for example AppConfig. The full application loads it, and the @DataJpaTest slice skips it.