Skip to content

Tests

Hub › Java › Intermediate › Tests

Goal

Write a repository slice test with @DataJpaTest and a web layer test with @WebMvcTest. After this page ./mvnw test passes with both test classes green, and you have seen the two main Spring Boot test slices in action.

Prerequisites

Why two test slices

Spring Boot offers test slices — annotations that start only the part of the application context relevant to one layer:

SliceStartsDoes NOT start
@DataJpaTestJPA, H2, repositoriesWeb layer, controllers
@WebMvcTestMockMvc, controllers, adviceJPA, real service beans

This keeps tests fast and focused. A @DataJpaTest confirms the repository query is correct without an HTTP request. A @WebMvcTest confirms the controller serializes correctly without a real database.

Repository slice test

Create src/test/java/com/example/itemsapi/ItemRepositoryTest.java:

java
package com.example.itemsapi;

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 ItemRepositoryTest {

    @Autowired
    ItemRepository repo;

    @Test
    void savesAndFinds() {
        Item it = new Item();
        it.setName("pen");
        Item saved = repo.save(it);
        assertThat(repo.findById(saved.getId())).isPresent();
    }
}

What it does:

  • @DataJpaTest starts an embedded H2 instance and the JPA layer. Each test runs in a transaction that is rolled back at the end, so tests are isolated.
  • repo.save(it) persists the item and returns it with the generated id populated.
  • assertThat(...).isPresent() uses AssertJ (bundled with spring-boot-starter-test) to verify the saved item can be retrieved by its ID.

Web layer test

Create src/test/java/com/example/itemsapi/ItemControllerTest.java:

java
package com.example.itemsapi;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(ItemController.class)
class ItemControllerTest {

    @Autowired
    MockMvc mvc;

    @MockBean
    ItemService service;

    @Test
    void rejectsBlankName() throws Exception {
        mvc.perform(post("/items")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\":\"\"}"))
           .andExpect(status().isBadRequest());
    }
}

What it does:

  • @WebMvcTest(ItemController.class) starts MockMvc and the ItemController, ApiExceptionHandler, and the validation infrastructure — nothing else.
  • @MockBean ItemService service — Spring replaces the real ItemService bean with a Mockito mock. The test does not reach the repository at all.
  • mvc.perform(post("/items")...) — sends a fake HTTP POST with an empty name field.
  • .andExpect(status().isBadRequest()) — asserts the response is HTTP 400, which our ApiExceptionHandler sets when MethodArgumentNotValidException is thrown.

Verify: tests pass

bash
./mvnw test

Expected output (last lines):

[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

If ItemControllerTest fails with a NoSuchBeanDefinitionException for ItemService, check that @MockBean is present — @WebMvcTest does not load service beans automatically.

What you have built

At the end of this tier your project has:

  • An Item entity mapped to an H2 table.
  • An ItemRepository with zero SQL.
  • An ItemService that encapsulates list, get-by-id, and create.
  • An ItemController that exposes GET /items, GET /items/{id}, and POST /items.
  • An ApiExceptionHandler that returns clean JSON for 404 and 400 errors.
  • Two test slices that cover the repository and the web layer independently.

The next step is to swap H2 for a persistent database (PostgreSQL) and deploy the application — that is the Advanced tier.