java-testing
Skill1 starsChanged 6 months agoMITView on GitHub
Java testing patterns for Spring Boot projects using JUnit 5, Mockito, MockMvc, and MyBatis Plus. Follows TDD methodology with layered test strategies.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- diego-tobalina/vibe-codingtheir own version, first seen Jan 30, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- diego-tobalina/vibe-coding★ 1same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository20 other files it gives its agents.
- finishing-a-development-branchSkill
- receiving-code-reviewSkill
- requesting-code-reviewSkill
- systematic-debuggingSkill
- verification-before-completionSkill
- api-designSkill
- database-migrationsSkill
- fastapi-patternsSkill
- logging-standardsSkill
- python-patternsSkill
- redis-patternsSkill
- springboot-patternsSkill
- security-reviewSkill
- using-superpowersSkill
- python-testingSkill
- test-driven-developmentSkill
- brainstormingSkill
- executing-plansSkill
- subagent-driven-developmentSkill
- writing-plansSkill
The file
We split this skill into the rules it gives an agent
2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 36
Prefer unit and slice tests.
- Very specific: line 87
Use @ParameterizedTest for table-driven cases — the Java equivalent of Go's table-driven tests.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Java Testing Patterns
Comprehensive testing patterns for Spring Boot + MyBatis Plus projects following TDD methodology.
When to Activate
- Writing new Java methods, services, or controllers
- Adding test coverage to existing code
- Following TDD workflow in Spring Boot projects
- Testing MyBatis Plus mapper queries
- Verifying REST API behavior with MockMvc
TDD Workflow for Java
RED → Write a failing test first (@Test, compile error is acceptable)
GREEN → Write minimal code to pass the test
REFACTOR → Improve code while keeping tests green
REPEAT → Continue with next requirementTest Layering Strategy
| Layer | Tool | Scope |
| Unit | JUnit 5 + Mockito | Service, Converter, Entity business methods |
| Slice | @WebMvcTest + MockMvc | Controller layer only |
| Slice | @DataJpaTest / MyBatis slice | Mapper + SQL queries |
| Integration | @SpringBootTest | Full context, critical flows only |
Prefer unit and slice tests. Use @SpringBootTest sparingly — it is slow.
Unit Tests (Service Layer)
@ExtendWith(MockitoExtension.class)
class AssetServiceTest {
@InjectMocks
private AssetServiceImpl assetService;
@Mock
private AssetMapper assetMapper;
@Mock
private RedisUtil redisUtil;
@Test
@DisplayName("充值成功:MySQL更新后Redis递增")
void recharge_success() {
// Arrange
Long advId = 1L;
BigDecimal amount = new BigDecimal("100.00");
AssetDO asset = AssetDO.create(advId, amount);
when(assetMapper.selectByAdvId(advId)).thenReturn(asset);
when(redisUtil.hmIncrAtomic(anyString(), anyString(), any(), anyLong()))
.thenReturn(100.0);
// Act
assetService.recharge(advId, amount);
// Assert
verify(assetMapper).updateByIdIgnoreTenant(any(AssetDO.class));
verify(redisUtil).hmIncrAtomic(anyString(), anyString(), any(), anyLong());
}
@Test
@DisplayName("Redis失败时抛出BusinessException触发MySQL回滚")
void recharge_redisFailure_throwsBusinessException() {
Long advId = 1L;
when(assetMapper.selectByAdvId(advId)).thenReturn(AssetDO.create(advId, BigDecimal.ZERO));
when(redisUtil.hmIncrAtomic(anyString(), anyString(), any(), anyLong()))
.thenReturn(null); // Redis failure
assertThrows(BusinessException.class, () -> assetService.recharge(advId, new BigDecimal("50")));
}
}Parameterized Tests
Use @ParameterizedTest for table-driven cases — the Java equivalent of Go's table-driven tests.
@ParameterizedTest(name = "{0}")
@MethodSource("rechargeAmountProvider")
@DisplayName("充值金额边界校验")
void recharge_amountBoundary(String scenario, BigDecimal amount, boolean shouldThrow) {
if (shouldThrow) {
assertThrows(BusinessException.class, () -> assetService.recharge(1L, amount));
} else {
assertDoesNotThrow(() -> assetService.recharge(1L, amount));
}
}
static Stream<Arguments> rechargeAmountProvider() {
return Stream.of(
Arguments.of("零金额拒绝", BigDecimal.ZERO, true),
Arguments.of("负金额拒绝", new BigDecimal("-1"), true),
Arguments.of("正常金额通过", new BigDecimal("100"), false),
Arguments.of("最小金额通过", new BigDecimal("0.01"), false)
);
}Controller Slice Test (MockMvc)
@WebMvcTest(AssetController.class)
class AssetControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private AssetService assetService;
@Autowired
private ObjectMapper objectMapper;
@Test
@DisplayName("充值接口:参数合法时返回200")
void recharge_validRequest_returns200() throws Exception {
RechargeReq req = new RechargeReq();
req.setAdvId(1L);
req.setAmount(new BigDecimal("100.00"));
mockMvc.perform(post("/asset/recharge")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(req)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").value(true));
}
@Test
@DisplayName("充值接口:金额为空时返回400")
void recharge_missingAmount_returns400() throws Exception {
mockMvc.perform(post("/asset/recharge")
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isBadRequest());
}
}MyBatis Plus Mapper Test
@MybatisPlusTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(locations = "classpath:application-test.yml")
class AssetMapperTest {
@Autowired
private AssetMapper assetMapper;
@Test
@DisplayName("按advId查询资产")
void selectByAdvId_existingId_returnsAsset() {
// Arrange — use @Sql or insert directly
AssetDO asset = AssetDO.create(999L, new BigDecimal("500.00"));
assetMapper.insert(asset);
// Act
AssetDO result = assetMapper.selectByAdvId(999L);
// Assert
assertThat(result).isNotNull();
assertThat(result.getPackageBalance()).isEqualByComparingTo("500.00");
}
@Test
@DisplayName("跨租户查询:@InterceptorIgnore 不添加 WHERE adv_id 条件")
void selectListIgnoreTenant_returnsAllTenants() {
List<AssetDO> results = assetMapper.selectListIgnoreTenant(new QueryWrapper<>());
// Should include records from multiple advIds without filtering
assertThat(results).isNotEmpty();
}
}Mockito Patterns
Verify Interaction Order
@Test
@DisplayName("MySQL先于Redis更新")
void update_mysqlBeforeRedis() {
InOrder inOrder = inOrder(assetMapper, redisUtil);
assetService.recharge(1L, new BigDecimal("100"));
inOrder.verify(assetMapper).updateByIdIgnoreTenant(any());
inOrder.verify(redisUtil).hmIncrAtomic(anyString(), anyString(), any(), anyLong());
}Capture Arguments
@Test
@DisplayName("创建资产时使用静态工厂方法")
void createAsset_usesStaticFactory() {
ArgumentCaptor<AssetDO> captor = ArgumentCaptor.forClass(AssetDO.class);
assetService.initAsset(1L);
verify(assetMapper).insert(captor.capture());
AssetDO saved = captor.getValue();
assertThat(saved.getAdvId()).isEqualTo(1L);
assertThat(saved.getPackageBalance()).isEqualByComparingTo(BigDecimal.ZERO);
}Test Coverage with JaCoCo
// build.gradle
jacocoTestReport {
reports {
xml.required = true
html.required = true
}
}
jacocoTestCoverageVerification {
violationRules {
rule {
limit {
minimum = 0.80 // 80% minimum
}
}
}
}
test.finalizedBy jacocoTestReport
check.dependsOn jacocoTestCoverageVerification# Run tests with coverage
./gradlew test jacocoTestReport
# View report
open build/reports/jacoco/test/html/index.htmlCoverage Targets
| Code Type | Target |
| Business logic (Service) | 85%+ |
| Controllers | 80%+ |
| Mappers (SQL tested via slice tests) | Exclude generated |
| Entity business methods | 90%+ |
Per-layer thresholds are enforced via JaCoCo in rules/common/testing.md.
Test File Organization
src/
├── main/java/com/opt/advertiser/
│ ├── controller/AssetController.java
│ ├── service/impl/AssetServiceImpl.java
│ └── mapper/AssetMapper.java
└── test/java/com/opt/advertiser/
├── controller/AssetControllerTest.java # @WebMvcTest slice
├── service/AssetServiceTest.java # @ExtendWith(MockitoExtension)
└── mapper/AssetMapperTest.java # @MybatisPlusTest sliceCommon Anti-Patterns
// ❌ 错误:在Service中直接new依赖,无法Mock
class AssetServiceImpl {
private final AssetMapper mapper = new AssetMapperImpl(); // 无法Mock
}
// ✅ 正确:构造器注入,可以Mock
class AssetServiceImpl {
private final AssetMapper mapper;
public AssetServiceImpl(AssetMapper mapper) { this.mapper = mapper; }
}
// ❌ 错误:测试实现细节(调用了几次某方法)
verify(mapper, times(1)).selectById(any()); // 脆弱
// ✅ 正确:测试业务行为
assertThat(result.getBalance()).isEqualByComparingTo("200.00");
// ❌ 错误:一个测试验证多个场景
@Test
void testAll() {
// 测试创建 + 更新 + 删除 + 异常 ...
}
// ✅ 正确:一个测试一个行为
@Test void create_success() { ... }
@Test void create_duplicateAdvId_throwsException() { ... }Running Tests
# Run all tests
./gradlew test
# Run specific test class
./gradlew test --tests "com.opt.advertiser.service.AssetServiceTest"
# Run with coverage
./gradlew test jacocoTestReport
# Run with verbose output
./gradlew test --infoRemember: Tests for Spring Boot Java projects should be layered. Most logic lives in unit tests with Mockito. Reserve @SpringBootTest for critical integration flows only — it starts the full context and is expensive.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

