Learn Spring Boot - Testing & Quality Assurance
Episode 17 of 24

Learn Spring Boot - Testing & Quality Assurance

This episode teaches quality assurance: unit testing with JUnit 5 and Mockito, integration testing with @SpringBootTest and @WebMvcTest, contract testing with Spring Cloud Contract, and Testcontainers for databases and service dependencies.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Code without tests is unfinished code. Episode 17 covers testing and quality assurance in Spring Boot — from fast unit tests to realistic integration tests with real containers.

You'll learn to write unit tests with JUnit 5 and Mockito, use slice tests like @WebMvcTest, test interactions between services with Testcontainers, and understand contract testing for microservices.

Unit Testing with JUnit 5 and Mockito

Test Structure

Spring Boot provides a test starter that bundles JUnit 5, Mockito, AssertJ, and more:

Test starter dependency
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

Unit Testing a Service with Mocks

A unit test focuses on a single class; dependencies are replaced with mocks. Here's an example testing a service that depends on a repository:

Unit test with Mockito
@ExtendWith(MockitoExtension.class)
class ItemServiceTest {
 
    @Mock
    private ItemRepository repository;
 
    @InjectMocks
    private ItemService service;
 
    @Test
    void cariItemBerdasarkanId() {
        Item item = new Item(1L, "Laptop");
        when(repository.findById(1L)).thenReturn(Optional.of(item));
 
        Item hasil = service.findById(1L);
 
        assertThat(hasil.getName()).isEqualTo("Laptop");
        verify(repository).findById(1L);
    }
}

when(...).thenReturn(...) sets the mock behavior, and verify(...) ensures the method is actually called. This unit test runs in milliseconds without a database.

Integration Testing with @SpringBootTest

Loading the Full Context

To make sure all beans are wired correctly, use @SpringBootTest — the test loads the entire application context:

Basic integration test
@SpringBootTest
@AutoConfigureMockMvc
class ItemControllerTest {
 
    @Autowired
    private MockMvc mockMvc;
 
    @Test
    void listItemsMengembalikanStatusOk() throws Exception {
        mockMvc.perform(get("/api/items"))
                .andExpect(status().isOk());
    }
}

MockMvc sends fake HTTP requests without starting a real server — fast and deterministic. This test verifies that the controller, service, and other beans are wired together correctly.

Slice Tests with @WebMvcTest

To test a controller in isolation without loading the whole context, use @WebMvcTest. Only the web layer is loaded; other dependencies are mocked:

WebMvc slice test
@WebMvcTest(ItemController.class)
class ItemControllerSliceTest {
 
    @Autowired
    private MockMvc mockMvc;
 
    @MockBean
    private ItemService service;
 
    @Test
    void getItemMengembalikan404() throws Exception {
        when(service.findById(99L))
                .thenThrow(new ItemNotFoundException(99L));
 
        mockMvc.perform(get("/api/items/99"))
                .andExpect(status().isNotFound());
    }
}

@WebMvcTest is much faster than @SpringBootTest because it only prepares the web layer. This test proves that the HTTP behavior — including the error handling from episode 7 — works correctly.

Testcontainers for Databases and Dependencies

Why Test with a Real Database

In-memory databases like H2 often behave differently from PostgreSQL. Testcontainers runs real Docker containers during tests, so tests run in the same environment as production:

Test with Testcontainers
@Testcontainers
@SpringBootTest
class ItemRepositoryTest {
 
    @Container
    static PostgreSQLContainer<?> postgres =
            new PostgreSQLContainer<>("postgres:16")
                    .withDatabaseName("belajar_test");
 
    @DynamicPropertySource
    static void setProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }
 
    @Autowired
    private ItemRepository repository;
    // test query sungguhan di PostgreSQL
}

The PostgreSQL container starts before the test and stops after it. Datasource properties are pointed at the container via @DynamicPropertySource. Make sure Docker is running on your development machine.

Testing Other Dependencies

Testcontainers also provides modules for Redis, Kafka, Elasticsearch, and more. The same pattern — declare a @Container, point properties at it, run the tests — applies to all service dependencies.

Contract Testing with Spring Cloud Contract

Keeping Contracts Between Services

In microservices, an unexpected change in one service can break another. Contract testing ensures both sides honor an agreed contract. With Spring Cloud Contract, contracts are written on the producer side in a Groovy DSL — for example, a GET /api/items/1 request is expected to reply with status 200 and a body containing id and name.

This contract generates tests on the producer side and stubs on the consumer side. Any contract change is detected immediately at build time — before it breaks integrations at runtime.

A Balanced Testing Strategy

A good strategy combines all levels:

  • Unit tests for business logic — fast, numerous, run in CI.
  • Slice tests for controllers and repositories — medium, focused per layer.
  • Integration tests with Testcontainers for critical end-to-end flows.
  • Contract tests for the boundaries between services in microservices.
Run all tests
./mvnw test

The command ./mvnw test runs the whole suite. In CI/CD (episode 21), this command becomes the gate before the application is deployed.

Closing

Episode 17 equipped you with quality assurance: unit testing with JUnit 5 and Mockito, integration testing with @SpringBootTest and @WebMvcTest, contract testing with Spring Cloud Contract, and Testcontainers for testing real databases and dependencies.

Key takeaways:

  • spring-boot-starter-test provides JUnit 5, Mockito, and AssertJ.
  • Unit tests use mocks and run in milliseconds.
  • @SpringBootTest loads the whole context; @WebMvcTest only the web layer.
  • Testcontainers runs a real database for realistic integration tests.
  • Contract testing protects the boundaries between services in microservices.
  • Combine unit, slice, integration, and contract tests in a balanced way.

In the next episode, episode 18, we'll discuss modular architecture and patterns — clean, hexagonal, and onion architectures; feature module structure and package-by-feature; popular design patterns; and event-driven architecture with Spring Events and Kafka.

Learn Spring Boot - Testing & Quality Assurance | Learn Spring Boot