java-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Java Testing Skill

Java测试技能

Write comprehensive tests for Java applications with modern testing practices.
使用现代测试实践为Java应用程序编写全面的测试。

Overview

概述

This skill covers Java testing with JUnit 5, Mockito, AssertJ, and integration testing with Spring Boot Test and Testcontainers. Includes TDD patterns and test coverage strategies.
本技能涵盖使用JUnit 5、Mockito、AssertJ进行Java测试,以及使用Spring Boot Test和Testcontainers进行集成测试。包含TDD模式和测试覆盖率策略。

When to Use This Skill

适用场景

Use when you need to:
  • Write unit tests with JUnit 5
  • Create mocks with Mockito
  • Build integration tests with Testcontainers
  • Implement TDD/BDD practices
  • Improve test coverage
当你需要以下操作时使用:
  • 使用JUnit 5编写单元测试
  • 使用Mockito创建模拟对象
  • 使用Testcontainers构建集成测试
  • 实施TDD/BDD实践
  • 提升测试覆盖率

Topics Covered

涵盖主题

JUnit 5

JUnit 5

  • @Test, @Nested, @DisplayName
  • @ParameterizedTest with sources
  • Lifecycle annotations
  • Extensions and custom annotations
  • @Test、@Nested、@DisplayName注解
  • 带数据源的@ParameterizedTest
  • 生命周期注解
  • 扩展与自定义注解

Mockito

Mockito

  • @Mock, @InjectMocks, @Spy
  • Stubbing (when/thenReturn)
  • Verification (verify, times)
  • BDD style (given/willReturn)
  • @Mock、@InjectMocks、@Spy注解
  • 存根(when/thenReturn)
  • 验证(verify、times)
  • BDD风格(given/willReturn)

AssertJ

AssertJ

  • Fluent assertions
  • Collection assertions
  • Exception assertions
  • Custom assertions
  • 流式断言
  • 集合断言
  • 异常断言
  • 自定义断言

Integration Testing

集成测试

  • @SpringBootTest slices
  • Testcontainers setup
  • MockMvc for APIs
  • Database testing
  • @SpringBootTest切片
  • Testcontainers配置
  • 用于API的MockMvc
  • 数据库测试

Quick Reference

快速参考

java
// Unit Test with Mockito
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("Should find user by ID")
    void shouldFindUserById() {
        // Given
        User user = new User(1L, "John");
        given(userRepository.findById(1L)).willReturn(Optional.of(user));

        // When
        Optional<User> result = userService.findById(1L);

        // Then
        assertThat(result)
            .isPresent()
            .hasValueSatisfying(u ->
                assertThat(u.getName()).isEqualTo("John"));
        then(userRepository).should().findById(1L);
    }
}

// Parameterized Test
@ParameterizedTest
@CsvSource({
    "valid@email.com, true",
    "invalid-email, false",
    "'', false"
})
void shouldValidateEmail(String email, boolean expected) {
    assertThat(validator.isValid(email)).isEqualTo(expected);
}

// Integration Test with Testcontainers
@Testcontainers
@SpringBootTest
class OrderRepositoryIT {

    @Container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:15");

    @DynamicPropertySource
    static void configure(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private OrderRepository repository;

    @Test
    void shouldPersistOrder() {
        Order saved = repository.save(new Order("item", 100.0));
        assertThat(saved.getId()).isNotNull();
    }
}

// API Test with MockMvc
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        given(userService.findById(1L))
            .willReturn(Optional.of(new User(1L, "John")));

        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"));
    }
}
java
// Unit Test with Mockito
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("Should find user by ID")
    void shouldFindUserById() {
        // Given
        User user = new User(1L, "John");
        given(userRepository.findById(1L)).willReturn(Optional.of(user));

        // When
        Optional<User> result = userService.findById(1L);

        // Then
        assertThat(result)
            .isPresent()
            .hasValueSatisfying(u ->
                assertThat(u.getName()).isEqualTo("John"));
        then(userRepository).should().findById(1L);
    }
}

// Parameterized Test
@ParameterizedTest
@CsvSource({
    "valid@email.com, true",
    "invalid-email, false",
    "'', false"
})
void shouldValidateEmail(String email, boolean expected) {
    assertThat(validator.isValid(email)).isEqualTo(expected);
}

// Integration Test with Testcontainers
@Testcontainers
@SpringBootTest
class OrderRepositoryIT {

    @Container
    static PostgreSQLContainer<?> postgres =
        new PostgreSQLContainer<>("postgres:15");

    @DynamicPropertySource
    static void configure(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private OrderRepository repository;

    @Test
    void shouldPersistOrder() {
        Order saved = repository.save(new Order("item", 100.0));
        assertThat(saved.getId()).isNotNull();
    }
}

// API Test with MockMvc
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserService userService;

    @Test
    void shouldReturnUser() throws Exception {
        given(userService.findById(1L))
            .willReturn(Optional.of(new User(1L, "John")));

        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"));
    }
}

Test Data Builders

测试数据构建器

java
public class UserTestBuilder {
    private Long id = 1L;
    private String name = "John Doe";
    private String email = "john@example.com";
    private boolean active = true;

    public static UserTestBuilder aUser() {
        return new UserTestBuilder();
    }

    public UserTestBuilder withName(String name) {
        this.name = name;
        return this;
    }

    public UserTestBuilder inactive() {
        this.active = false;
        return this;
    }

    public User build() {
        return new User(id, name, email, active);
    }
}

// Usage
User user = aUser().withName("Jane").inactive().build();
java
public class UserTestBuilder {
    private Long id = 1L;
    private String name = "John Doe";
    private String email = "john@example.com";
    private boolean active = true;

    public static UserTestBuilder aUser() {
        return new UserTestBuilder();
    }

    public UserTestBuilder withName(String name) {
        this.name = name;
        return this;
    }

    public UserTestBuilder inactive() {
        this.active = false;
        return this;
    }

    public User build() {
        return new User(id, name, email, active);
    }
}

// 使用示例
User user = aUser().withName("Jane").inactive().build();

Coverage Goals

覆盖率目标

xml
<!-- JaCoCo configuration -->
<configuration>
    <rules>
        <rule>
            <element>BUNDLE</element>
            <limits>
                <limit>
                    <counter>LINE</counter>
                    <value>COVEREDRATIO</value>
                    <minimum>0.80</minimum>
                </limit>
            </limits>
        </rule>
    </rules>
</configuration>
xml
<!-- JaCoCo configuration -->
<configuration>
    <rules>
        <rule>
            <element>BUNDLE</element>
            <limits>
                <limit>
                    <counter>LINE</counter>
                    <value>COVEREDRATIO</value>
                    <minimum>0.80</minimum>
                </limit>
            </limits>
        </rule>
    </rules>
</configuration>

Troubleshooting

故障排除

Common Issues

常见问题

ProblemCauseSolution
Mock not workingMissing @ExtendWithAdd MockitoExtension
NPE in testMock not initializedCheck @InjectMocks
Flaky testShared stateIsolate test data
Context failsMissing beanUse @MockBean
问题原因解决方案
模拟对象不生效缺少@ExtendWith添加MockitoExtension
测试中出现空指针异常(NPE)模拟对象未初始化检查@InjectMocks配置
不稳定测试共享状态隔离测试数据
上下文加载失败缺少Bean使用@MockBean

Debug Checklist

调试检查清单

□ Run single test in isolation
□ Check mock setup matches invocation
□ Verify @BeforeEach setup
□ Review @Transactional boundaries
□ Check for shared mutable state
□ 单独运行单个测试
□ 检查模拟对象设置是否与调用匹配
□ 验证@BeforeEach的设置
□ 检查@Transactional边界
□ 查找是否存在共享可变状态

Usage

使用方式

Skill("java-testing")
Skill("java-testing")

Related Skills

相关技能

  • java-testing-advanced
    - Advanced patterns
  • java-spring-boot
    - Spring test slices
  • java-testing-advanced
    - 高级模式
  • java-spring-boot
    - Spring测试切片