You can test repository-like classes without a real database by replacing the database dependency with a fake, mock, or in-memory implementation, depending on what you want to verify.
1. Use mocks for unit tests
If your class depends on a repository interface, mock it and verify behavior without touching a database.
Example with JUnit 5 and Mockito:
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
class UserServiceTest {
@Test
void returnsUserName() {
UserRepository userRepository = Mockito.mock(UserRepository.class);
when(userRepository.findById(1L))
.thenReturn(Optional.of(new User(1L, "Alice")));
UserService service = new UserService(userRepository);
String name = service.getUserName(1L);
assertThat(name).isEqualTo("Alice");
}
}
This is best when testing service logic, not repository implementation details.
2. Use a fake in-memory repository
If you have repository-like classes that are simple abstractions, create an in-memory fake.
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
class InMemoryUserRepository implements UserRepository {
private final Map<Long, User> users = new HashMap<>();
@Override
public Optional<User> findById(Long id) {
return Optional.ofNullable(users.get(id));
}
@Override
public User save(User user) {
users.put(user.id(), user);
return user;
}
}
Then use it in tests:
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class UserServiceTest {
@Test
void savesAndLoadsUser() {
UserRepository repository = new InMemoryUserRepository();
UserService service = new UserService(repository);
service.createUser(1L, "Alice");
assertThat(service.getUserName(1L)).isEqualTo("Alice");
}
}
This is useful when you want tests that are more realistic than mocks but still fast.
3. Use Spring @MockBean / @MockitoBean in Spring tests
For Spring MVC or service-layer tests, replace a repository bean with a mock.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Optional;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest
class UserServiceSpringTest {
@MockitoBean
private UserRepository userRepository;
@Autowired
private UserService userService;
@Test
void returnsUserName() {
when(userRepository.findById(1L))
.thenReturn(Optional.of(new User(1L, "Alice")));
assertThat(userService.getUserName(1L)).isEqualTo("Alice");
}
}
Use this when you want Spring wiring but not database access.
4. Use @DataJpaTest with an embedded database
If you are testing a Spring Data JPA repository itself, mocks are usually not enough. You need to verify queries, mappings, transactions, and entity relationships.
For that, use an embedded database such as H2:
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import static org.assertj.core.api.Assertions.assertThat;
@DataJpaTest
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Test
void findsByEmail() {
User user = new User();
user.setName("Alice");
user.setEmail("[email protected]");
userRepository.save(user);
User found = userRepository.findByEmail("[email protected]").orElseThrow();
assertThat(found.getName()).isEqualTo("Alice");
}
}
This does use a database, but not a “real” external one. It is fast and isolated.
5. Use Testcontainers for production-like repository tests
If your repository uses database-specific features, H2 may behave differently from PostgreSQL, MySQL, Oracle, etc.
In that case, use Testcontainers:
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@DataJpaTest
@Testcontainers
class UserRepositoryTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16");
@Test
void testRepository() {
// repository test here
}
}
This is not a local “real database” you manage manually, but it gives you much higher confidence.
Recommended approach
| What you are testing | Recommended technique |
|---|---|
| Service using a repository | Mock repository |
| Business logic with persistence-like behavior | Fake in-memory repository |
| Spring wiring without DB | @MockitoBean |
| JPA mappings and queries | @DataJpaTest |
| DB-specific SQL/features | Testcontainers |
Rule of thumb
Do not unit test Spring Data JPA repositories by mocking JPA internals like EntityManager unless your repository has significant custom logic.
Instead:
- Mock repositories when testing services.
- Use fakes when testing domain/application logic.
- Use
@DataJpaTestor Testcontainers when testing actual persistence behavior.
