Why this matters
- Full
@SpringBootTestloads every bean, every auto-configuration, and every external client — a suite of two hundred integration tests can take minutes. @WebMvcTestloads just the web layer; you verify HTTP status codes, JSON shapes, and validation without a database.@DataJpaTestloads 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
Test annotations and tools
@WebMvcTest— loads@Controllerbeans and MockMvc; auto-configures JSON serializers.@DataJpaTest— loads@Entityclasses and@Repositoryinterfaces 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
@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
@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
@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
@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)+MockMvcfor HTTP layer; mock services with@MockBean.@DataJpaTestfor repository query methods withTestEntityManagerto 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.