Spring Boot Cheatsheet
Testing
Use this Spring Boot reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Test Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>Includes: JUnit 5, Mockito, AssertJ, MockMvc, Hamcrest, JsonPath, H2 (in-memory DB), Testcontainers integration.
Test Slices — Choosing the Right Annotation
| Annotation | Loads | Use For |
|---|---|---|
@SpringBootTest | Full ApplicationContext | Integration tests |
@WebMvcTest(Ctrl.class) | Web layer only (no DB) | Controller unit tests |
@DataJpaTest | JPA + H2/Testcontainer only | Repository tests |
@JsonTest | Jackson mapper only | JSON serialization tests |
@RestClientTest | RestTemplate/WebClient + mock server | HTTP client tests |
@WebFluxTest | Reactive web layer | WebFlux controller tests |
@DataMongoTest | MongoDB layer | Mongo repository tests |
Full Integration Test (@SpringBootTest)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Testcontainers class OrderIntegrationTest { @Autowired TestRestTemplate restTemplate; @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16") .withDatabaseName("testdb"); @DynamicPropertySource static void props(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgres::getJdbcUrl); registry.add("spring.datasource.username", postgres::getUsername); registry.add("spring.datasource.password", postgres::getPassword); } @Test void createOrderReturns201() { var request = new CreateOrderRequest("item-1", 3); var response = restTemplate.postForEntity("/api/orders", request, OrderDto.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CREATED); assertThat(response.getBody()).isNotNull(); assertThat(response.getBody().quantity()).isEqualTo(3); } }
WebEnvironment Options
| Value | Behavior |
|---|---|
RANDOM_PORT | Starts real server on random port; use TestRestTemplate or WebTestClient |
DEFINED_PORT | Uses server.port (default 8080) |
MOCK | MockMvc-based; no real server (default) |
NONE | No web environment at all |
Controller Tests (@WebMvcTest)
@WebMvcTest(ProductController.class) class ProductControllerTest { @Autowired MockMvc mvc; @MockBean ProductService productService; // replaces real bean @Test void getProductReturns200() throws Exception { var dto = new ProductDto(1L, "Widget", new BigDecimal("9.99")); given(productService.findById(1L)).willReturn(dto); mvc.perform(get("/api/products/1") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.name").value("Widget")) .andExpect(jsonPath("$.price").value(9.99)); } @Test void createProductWithInvalidBodyReturns400() throws Exception { String body = """ { "name": "", "price": -1 } """; mvc.perform(post("/api/products") .contentType(MediaType.APPLICATION_JSON) .content(body)) .andExpect(status().isBadRequest()); } @Test @WithMockUser(roles = "ADMIN") void deleteRequiresAdminRole() throws Exception { mvc.perform(delete("/api/products/1")) .andExpect(status().isNoContent()); } }
Common MockMvc Methods
// Request builders get("/path") post("/path").contentType(APPLICATION_JSON).content(json) put("/path/{id}", 1L) delete("/path/{id}", 1L) multipart("/path").file("file", bytes) // RequestBuilders fluent options .param("page", "0") .header("Authorization", "Bearer token") .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) // Result matchers .andExpect(status().isOk()) .andExpect(status().isCreated()) .andExpect(status().isBadRequest()) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andExpect(jsonPath("$.id").value(1)) .andExpect(jsonPath("$.items", hasSize(3))) .andExpect(jsonPath("$.items[0].name").value("Widget")) .andExpect(header().string("Location", containsString("/api/products/"))) // Print request/response to console (debugging) .andDo(print()) // Capture the result MvcResult result = mvc.perform(get("/api/products")).andReturn(); String json = result.getResponse().getContentAsString();
Repository Tests (@DataJpaTest)
@DataJpaTest // auto-configures H2, JPA, and Flyway/schema; @Transactional per test class ProductRepositoryTest { @Autowired ProductRepository repo; @Autowired TestEntityManager em; @Test void findBySkuReturnsProduct() { var product = new Product(null, "Widget", "SKU-001", Status.ACTIVE); em.persistAndFlush(product); Optional<Product> found = repo.findBySku("SKU-001"); assertThat(found).isPresent(); assertThat(found.get().getName()).isEqualTo("Widget"); } }
Use Real DB with @DataJpaTest
@DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Testcontainers class ProductRepositoryTest { @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16"); @DynamicPropertySource static void props(DynamicPropertyRegistry reg) { reg.add("spring.datasource.url", postgres::getJdbcUrl); reg.add("spring.datasource.username", postgres::getUsername); reg.add("spring.datasource.password", postgres::getPassword); } // ... }
Pure Unit Tests (No Spring Context)
class OrderServiceTest { @Mock OrderRepository orderRepo; @Mock PaymentService paymentService; @InjectMocks OrderService orderService; // Mockito injects mocks @BeforeEach void setUp() { MockitoAnnotations.openMocks(this); } // Or use @ExtendWith(MockitoExtension.class) on the class @Test void placeOrderCallsPaymentService() { var req = new CreateOrderRequest(1L, "ITEM-1", 2); var order = new Order(1L, req.userId(), List.of()); given(orderRepo.save(any())).willReturn(order); orderService.place(req); verify(paymentService).charge(eq(1L), any(BigDecimal.class)); } @Test void placeOrderThrowsWhenItemNotFound() { given(orderRepo.save(any())).willThrow(new ResourceNotFoundException("Item", 99L)); assertThatThrownBy(() -> orderService.place(new CreateOrderRequest(1L, 99L, 1))) .isInstanceOf(ResourceNotFoundException.class) .hasMessageContaining("99"); } }
Mockito Quick Reference
// Stubbing given(service.findById(1L)).willReturn(dto); given(service.findById(anyLong())).willThrow(ResourceNotFoundException.class); given(service.findById(argThat(id -> id > 0))).willReturn(dto); // Void methods willDoNothing().given(service).delete(1L); willThrow(new RuntimeException()).given(service).delete(2L); // Argument matchers any(), anyLong(), anyString(), anyList() eq(1L), isNull(), isNotNull() argThat(obj -> obj.getId() > 0) // Verification verify(service).findById(1L); verify(service, times(2)).findById(anyLong()); verify(service, never()).delete(anyLong()); verify(service, atLeastOnce()).save(any()); verifyNoMoreInteractions(service); // Capture arguments ArgumentCaptor<Order> captor = ArgumentCaptor.forClass(Order.class); verify(repo).save(captor.capture()); assertThat(captor.getValue().getStatus()).isEqualTo(Status.PENDING);
AssertJ Quick Reference
// Strings assertThat(name).isEqualTo("Alice").startsWith("Al").hasSize(5); assertThat(email).contains("@").isNotBlank(); // Numbers assertThat(price).isGreaterThan(BigDecimal.ZERO).isLessThan(new BigDecimal("100")); assertThat(count).isEqualTo(3).isPositive(); // Collections assertThat(list).hasSize(3).contains("a","b").doesNotContain("x"); assertThat(list).extracting("name").containsExactly("Alice","Bob"); assertThat(list).allMatch(item -> item.isActive()); // Optional assertThat(optional).isPresent().contains(expectedValue); assertThat(optional).isEmpty(); // Exceptions assertThatThrownBy(() -> service.method()) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Invalid input"); assertThatCode(() -> service.safeMethod()).doesNotThrowAnyException(); // Objects assertThat(dto).isNotNull() .hasFieldOrPropertyWithValue("name", "Alice") .usingRecursiveComparison().isEqualTo(expectedDto);
JSON Tests (@JsonTest)
@JsonTest class ProductDtoJsonTest { @Autowired JacksonTester<ProductDto> json; @Test void serializesCorrectly() throws Exception { var dto = new ProductDto(1L, "Widget", new BigDecimal("9.99")); assertThat(json.write(dto)) .hasJsonPathValue("$.id") .hasJsonPathStringValue("$.name", "Widget"); } @Test void deserializesCorrectly() throws Exception { String content = """ {"id":1,"name":"Widget","price":9.99} """; assertThat(json.parse(content).getObject()) .usingRecursiveComparison() .isEqualTo(new ProductDto(1L, "Widget", new BigDecimal("9.99"))); } }
Test Properties and Configuration
// Override properties for a test class @SpringBootTest @TestPropertySource(properties = { "spring.mail.host=localhost", "feature.flag.enabled=true" }) // Or via a test application.properties // src/test/resources/application-test.properties // Activate a test profile @ActiveProfiles("test")
Testcontainers Reuse Pattern
// Shared container across all tests (reuse = true) @Testcontainers class AbstractIntegrationTest { @Container static final PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>("postgres:16").withReuse(true); @DynamicPropertySource static void configureProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", POSTGRES::getJdbcUrl); registry.add("spring.datasource.username", POSTGRES::getUsername); registry.add("spring.datasource.password", POSTGRES::getPassword); } } // Then extend: class UserRepositoryTest extends AbstractIntegrationTest { ... } class OrderRepositoryTest extends AbstractIntegrationTest { ... }
Common Pitfalls
@MockBeanresets application context — each unique@MockBeancombination causes a new context to load; minimize@MockBeanuse and share context between tests with@DirtiesContextor common base classes.@DataJpaTestrolls back transactions — by default each test method runs in a transaction that rolls back; add@Commitif you need the data to persist between test methods.@WebMvcTestdoes not load@Servicebeans — use@MockBeanfor all service-layer dependencies, or the test will fail withNoSuchBeanDefinitionException.- MockMvc vs
TestRestTemplate— MockMvc tests the servlet without starting a server (fast);TestRestTemplaterequiresWebEnvironment.RANDOM_PORTand tests the full HTTP stack. - Security auto-configuration in
@WebMvcTest— by default, Spring Security is active in@WebMvcTest; add@WithMockUseror excludeSecurityAutoConfiguration.class. - Testcontainers on CI — requires Docker; use
@Containeronstaticfields (shared per class) to reduce container startup time.