Skip to main content Home Skills LLMs & Models springboot-tdd Test-driven development for Spring Boot using JUnit 5, Mockito, MockMvc, Testcontainers, and JaCoCo. Use when adding features, fixing bugs, or refactoring.
bunx add-skill affaan-m/everything-claude-code -s springboot-tdd ai-agents anthropic claude claude-code developer-tools llm
Spring Boot TDD Workflow
TDD guidance for Spring Boot services with 80%+ coverage (unit + integration).
When to Use
New features or endpoints
Bug fixes or refactors
Adding data access logic or security rules
Workflow
Write tests first (they should fail)
Implement minimal code to pass
Refactor with tests green
Enforce coverage (JaCoCo)
Unit Tests (JUnit 5 + Mockito)
@ExtendWith(MockitoExtension.class)
class MarketServiceTest {
@Mock MarketRepository repo;
@InjectMocks MarketService service;
@Test
void createsMarket() {
CreateMarketRequest req = new CreateMarketRequest("name", "desc", Instant.now(), List.of("cat"));
when(repo.save(any())).thenAnswer(inv -> inv.getArgument(0));
Market result = service.create(req);
assertThat(result.name()).isEqualTo("name");
verify(repo).save(any());
}
}
Patterns:
Arrange-Act-Assert
Avoid partial mocks; prefer explicit stubbing
Use @ParameterizedTest for variants
Web Layer Tests (MockMvc)
@WebMvcTest(MarketController.class)
class MarketControllerTest {
@Autowired MockMvc mockMvc;
@MockBean MarketService marketService;
@Test
void returnsMarkets() throws Exception {
when(marketService.list(any())).thenReturn(Page.empty());
mockMvc.perform(get("/api/markets"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").isArray());
}
}
Integration Tests (SpringBootTest) @SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
class MarketIntegrationTest {
@Autowired MockMvc mockMvc;
@Test
void createsMarket() throws Exception {
mockMvc.perform(post("/api/markets")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name":"Test","description":"Desc","endDate":"2030-01-01T00:00:00Z","categories":["general"]}
"""))
.andExpect(status().isCreated());
}
}
Persistence Tests (DataJpaTest) @DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(TestContainersConfig.class)
class MarketRepositoryTest {
@Autowired MarketRepository repo;
@Test
void savesAndFinds() {
MarketEntity entity = new MarketEntity();
entity.setName("Test");
repo.save(entity);
Optional<MarketEntity> found = repo.findByName("Test");
assertThat(found).isPresent();
}
}
Testcontainers
Use reusable containers for Postgres/Redis to mirror production
Wire via @DynamicPropertySource to inject JDBC URLs into Spring context
Coverage (JaCoCo) <plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.14</version>
<executions>
<execution>
<goals><goal>prepare-agent</goal></goals>
</execution>
<execution>
<id>report</id>
<phase>verify</phase>
<goals><goal>report</goal></goals>
</execution>
</executions>
</plugin>
Assertions
Prefer AssertJ (assertThat) for readability
For JSON responses, use jsonPath
For exceptions: assertThatThrownBy(...)
Test Data Builders class MarketBuilder {
private String name = "Test";
MarketBuilder withName(String name) { this.name = name; return this; }
Market build() { return new Market(null, name, MarketStatus.ACTIVE); }
}
CI Commands
Maven: mvn -T 4 test or mvn verify
Gradle: ./gradlew test jacocoTestReport
Remember : Keep tests fast, isolated, and deterministic. Test behavior, not implementation details.