Spring Boot Testing

1 minute read

Spring Boot provides powerful test annotations that load only the parts of the application you need.


Test Slices

Annotation Loads Use For
@SpringBootTest Full context Integration tests
@WebMvcTest Controllers only Controller/API tests
@DataJpaTest JPA + H2 Repository tests
@MockBean Replace bean with mock Isolating dependencies

Unit Testing Services

@ExtendWith(MockitoExtension.class)
class BookServiceTest {

    @Mock
    private BookRepository bookRepository;

    @InjectMocks
    private BookService bookService;

    @Test
    void shouldCreateBook() {
        CreateBookRequest request = new CreateBookRequest("Clean Code", "Robert Martin", "978-0132350884", new BigDecimal("39.99"));
        Book saved = new Book("Clean Code", "Robert Martin", "978-0132350884", new BigDecimal("39.99"));
        saved.setId(1L);

        when(bookRepository.existsByIsbn(anyString())).thenReturn(false);
        when(bookRepository.save(any(Book.class))).thenReturn(saved);

        BookResponse result = bookService.create(request);

        assertEquals("Clean Code", result.title());
        assertEquals(1L, result.id());
        verify(bookRepository).save(any(Book.class));
    }

    @Test
    void shouldThrowWhenBookNotFound() {
        when(bookRepository.findById(99L)).thenReturn(Optional.empty());

        assertThrows(ResourceNotFoundException.class,
            () -> bookService.findById(99L));
    }
}

@WebMvcTest — Controller Tests

@WebMvcTest(BookController.class)
class BookControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private BookService bookService;

    @Test
    void shouldReturnAllBooks() throws Exception {
        List<BookResponse> books = List.of(
            new BookResponse(1L, "Clean Code", "Robert Martin", "978-0132350884",
                new BigDecimal("39.99"), "PUBLISHED", LocalDateTime.now())
        );
        when(bookService.findAll(any())).thenReturn(new PageImpl<>(books));

        mockMvc.perform(get("/api/books"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.content[0].title").value("Clean Code"))
            .andExpect(jsonPath("$.content[0].author").value("Robert Martin"));
    }

    @Test
    void shouldReturn404WhenNotFound() throws Exception {
        when(bookService.findById(99L))
            .thenThrow(new ResourceNotFoundException("Book", 99L));

        mockMvc.perform(get("/api/books/99"))
            .andExpect(status().isNotFound());
    }

    @Test
    void shouldValidateCreateRequest() throws Exception {
        String invalidJson = """
            {"title": "", "author": "", "price": -10}
            """;

        mockMvc.perform(post("/api/books")
                .contentType(MediaType.APPLICATION_JSON)
                .content(invalidJson))
            .andExpect(status().isBadRequest());
    }
}

@DataJpaTest — Repository Tests

@DataJpaTest
class BookRepositoryTest {

    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void shouldFindByAuthor() {
        Book book = new Book("Clean Code", "Robert Martin", "978-0132350884", new BigDecimal("39.99"));
        entityManager.persistAndFlush(book);

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

        assertEquals(1, found.size());
        assertEquals("Clean Code", found.get(0).getTitle());
    }

    @Test
    void shouldCheckIsbnExists() {
        Book book = new Book("Test", "Author", "123-4567890123", new BigDecimal("10.00"));
        entityManager.persistAndFlush(book);

        assertTrue(bookRepository.existsByIsbn("123-4567890123"));
        assertFalse(bookRepository.existsByIsbn("000-0000000000"));
    }
}

@SpringBootTest — Integration Tests

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class BookIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate;

    @Test
    void shouldCreateAndRetrieveBook() {
        CreateBookRequest request = new CreateBookRequest(
            "Integration Test Book", "Tester", "999-0000000001", new BigDecimal("25.00"));

        ResponseEntity<BookResponse> createResponse = restTemplate.postForEntity(
            "/api/books", request, BookResponse.class);

        assertEquals(HttpStatus.CREATED, createResponse.getStatusCode());

        Long bookId = createResponse.getBody().id();
        ResponseEntity<BookResponse> getResponse = restTemplate.getForEntity(
            "/api/books/" + bookId, BookResponse.class);

        assertEquals("Integration Test Book", getResponse.getBody().title());
    }
}

Key Takeaways

  1. Unit tests: @ExtendWith(MockitoExtension.class) + @Mock — no Spring context.
  2. @WebMvcTest: test controllers with MockMvc — fast, isolated.
  3. @DataJpaTest: test repositories with embedded H2.
  4. @SpringBootTest: full integration test — slow but thorough.
  5. Use @MockBean to replace real beans with mocks in Spring tests.

What’s Next?

Next: Dockerizing Spring Boot — Dockerfile, multi-stage builds, and Docker Compose.

Updated: