How do I test repository-like classes without a real database?

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 @DataJpaTest or Testcontainers when testing actual persistence behavior.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.