PrepZone Logo
PrepZone

@WebMvcTest, @DataJpaTest, MockMvc

Testing layers in isolation with slice annotations and MockMvc.

Why this matters

  • Full @SpringBootTest loads every bean, every auto-configuration, and every external client — a suite of two hundred integration tests can take minutes.
  • @WebMvcTest loads just the web layer; you verify HTTP status codes, JSON shapes, and validation without a database.
  • @DataJpaTest loads JPA and an in-memory database; you verify query methods and entity mappings in isolation.
ControllerHTTP in/out — @RestController
ServiceBusiness rules — @Service
RepositoryDatabase — @Repository
DatabasePostgreSQL / H2
Each layer has one job. Dependencies point inward — controllers never touch the database directly.

Test annotations and tools

  • @WebMvcTest — loads @Controller beans and MockMvc; auto-configures JSON serializers.
  • @DataJpaTest — loads @Entity classes and @Repository interfaces with an embedded database.
  • @MockBean — replaces a Spring bean with a Mockito mock inside the test context.
  • MockMvc — sends fake HTTP requests and asserts on status, headers, and response body.
  • @JsonTest — tests JSON serialization/deserialization of DTOs without any web layer.

Controller slice test

Java
@WebMvcTest(BookController.class)
class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookService bookService;

    @Test
    void getBook_returns200WithBody() throws Exception {
        BookDetail detail = new BookDetail("978-0134685991", "Effective Java", "Joshua Bloch", 45.99);
        when(bookService.getByIsbn("978-0134685991")).thenReturn(detail);

        mockMvc.perform(get("/api/books/978-0134685991"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.title").value("Effective Java"))
                .andExpect(jsonPath("$.price").value(45.99));
    }

    @Test
    void getBook_returns404WhenMissing() throws Exception {
        when(bookService.getByIsbn("unknown"))
                .thenThrow(new BookNotFoundException("unknown"));

        mockMvc.perform(get("/api/books/unknown"))
                .andExpect(status().isNotFound())
                .andExpect(jsonPath("$.title").value("Book Not Found"));
    }
}

Validation and request body tests

Java
@Test
void createBook_rejectsInvalidIsbn() throws Exception {
    String body = """
            {"isbn": "", "title": "Test", "author": "Author", "price": -1}
            """;

    mockMvc.perform(post("/api/books")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(body))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.violations").isArray());
}

Repository slice test

Java
@DataJpaTest
class BookRepositoryTest {

    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void findByAuthor_returnsMatchingBooks() {
        entityManager.persist(new Book("978-1", "Clean Code", "Robert Martin", 38.00));
        entityManager.persist(new Book("978-2", "Clean Architecture", "Robert Martin", 42.00));
        entityManager.persist(new Book("978-3", "Refactoring", "Kent Beck", 50.00));
        entityManager.flush();

        List<Book> results = bookRepository.findByAuthor("Robert Martin");

        assertThat(results).hasSize(2);
        assertThat(results).extracting(Book::getTitle)
                .containsExactlyInAnyOrder("Clean Code", "Clean Architecture");
    }
}

Pure unit test for service logic

Java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock private OrderRepository orderRepository;
    @Mock private InventoryService inventoryService;
    @InjectMocks private OrderService orderService;

    @Test
    void placeOrder_reservesInventoryBeforeSaving() {
        PlaceOrderRequest request = new PlaceOrderRequest("cust-1", List.of(
                new OrderLineRequest("978-1", 2)));
        when(inventoryService.checkAvailability(any())).thenReturn(true);
        when(orderRepository.save(any())).thenAnswer(inv -> inv.getArgument(0));

        orderService.placeOrder(request);

        verify(inventoryService).reserve(any());
        verify(orderRepository).save(any(Order.class));
    }
}

Quick recall

Everything you need if you only revisit this box.

  • @WebMvcTest(Controller.class) + MockMvc for HTTP layer; mock services with @MockBean.
  • @DataJpaTest for repository query methods with TestEntityManager to seed data.
  • Pure @ExtendWith(MockitoExtension.class) tests for service logic with no Spring context at all.
  • Slice tests are fast because they exclude unrelated auto-configuration.
  • Test the HTTP contract (status, JSON shape, validation errors) separately from business rules.

Test yourself

Answer these before moving on — recall is what makes it stick.