Spring Boot 单元测试使用教程(仅供参考)

单元测试是软件开发中至关重要的一环,Spring Boot 提供了强大的测试支持。以下是 Spring Boot 单元测试的详细教程。


1. 准备工作

1.1 添加测试依赖

pom.xml 中添加测试相关依赖:

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- 如果需要MockMvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- 如果需要AssertJ -->
<dependency>
    <groupId>org.assertj</groupId>
    <artifactId>assertj-core</artifactId>
    <version>3.24.2</version>
    <scope>test</scope>
</dependency>

1.2 测试类基本结构

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class MyApplicationTests {

    @Test
    public void contextLoads() {
        // 测试Spring上下文是否正常加载
    }
}

2. 不同类型的测试

2.1 服务层测试

java 复制代码
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
public class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    public void testGetUserById() {
        // 准备测试数据
        User mockUser = new User(1L, "test@example.com", "Test User");
        
        // 定义mock行为
        when(userRepository.findById(1L)).thenReturn(Optional.of(mockUser));
        
        // 调用测试方法
        User result = userService.getUserById(1L);
        
        // 验证结果
        assertEquals("Test User", result.getName());
        verify(userRepository, times(1)).findById(1L);
    }
}

2.2 控制器层测试

使用MockMvc
java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void testGetUser() throws Exception {
        mockMvc.perform(get("/api/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("Test User"));
    }
}
使用WebTestClient (WebFlux)
java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest
@AutoConfigureWebTestClient
public class UserControllerWebTestClientTest {

    @Autowired
    private WebTestClient webTestClient;

    @Test
    public void testGetUser() {
        webTestClient.get().uri("/api/users/1")
                    .exchange()
                    .expectStatus().isOk()
                    .expectBody()
                    .jsonPath("$.name").isEqualTo("Test User");
    }
}

2.3 数据库测试

使用@DataJpaTest
java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import static org.assertj.core.api.Assertions.*;

@DataJpaTest
public class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository userRepository;

    @Test
    public void testFindByEmail() {
        // 准备测试数据
        User user = new User("test@example.com", "Test User");
        entityManager.persist(user);
        entityManager.flush();
        
        // 调用测试方法
        User found = userRepository.findByEmail(user.getEmail());
        
        // 验证结果
        assertThat(found.getEmail()).isEqualTo(user.getEmail());
    }
}
使用@SpringBootTest + 测试数据库
java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.transaction.annotation.Transactional;
import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ActiveProfiles("test")
@Transactional
public class UserServiceIntegrationTest {

    @Autowired
    private UserService userService;

    @Autowired
    private UserRepository userRepository;

    @Test
    public void testCreateUser() {
        User newUser = new User("new@example.com", "New User");
        
        User savedUser = userService.createUser(newUser);
        
        assertNotNull(savedUser.getId());
        assertEquals("New User", savedUser.getName());
        
        User found = userRepository.findById(savedUser.getId()).orElse(null);
        assertEquals("New User", found.getName());
    }
}

3. 常用测试技巧

3.1 参数化测试

java 复制代码
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ParameterizedTests {

    @ParameterizedTest
    @ValueSource(strings = {"racecar", "radar", "madam"})
    public void testPalindromes(String candidate) {
        assertTrue(StringUtils.isPalindrome(candidate));
    }
}

3.2 测试异常

java 复制代码
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ExceptionTest {

    @Test
    public void testException() {
        UserService userService = new UserService();
        
        assertThrows(UserNotFoundException.class, () -> {
            userService.getUserById(999L);
        });
    }
}

3.3 测试私有方法

虽然不推荐直接测试私有方法,但有时确实需要:

java 复制代码
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;

public class PrivateMethodTest {

    @Test
    public void testPrivateMethod() throws Exception {
        MyService service = new MyService();
        
        Method method = MyService.class.getDeclaredMethod("privateMethod", String.class);
        method.setAccessible(true);
        
        String result = (String) method.invoke(service, "input");
        assertEquals("expected", result);
    }
}

4. 测试配置

4.1 使用测试配置文件

创建 src/test/resources/application-test.properties

Clojure 复制代码
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true

然后在测试类上使用:

java 复制代码
@ActiveProfiles("test")

4.2 使用测试切片

Spring Boot 提供了多种测试切片注解:

  • @WebMvcTest - 只测试MVC层

  • @DataJpaTest - 只测试JPA组件

  • @JsonTest - 只测试JSON序列化

  • @RestClientTest - 只测试REST客户端

    java 复制代码
    @WebMvcTest(UserController.class)
    public class UserControllerSliceTest {
    
        @Autowired
        private MockMvc mockMvc;
    
        @MockBean
        private UserService userService;
    
        @Test
        public void testGetUser() throws Exception {
            when(userService.getUserById(1L)).thenReturn(new User(1L, "test@example.com", "Test User"));
            
            mockMvc.perform(get("/api/users/1"))
                   .andExpect(status().isOk())
                   .andExpect(jsonPath("$.name").value("Test User"));
        }
    }

5. 测试最佳实践

  1. 命名规范 :测试方法名应清晰表达测试意图,如 shouldReturnUserWhenValidIdProvided()

  2. 单一职责:每个测试方法只测试一个功能点

  3. AAA模式:遵循Arrange-Act-Assert模式组织测试代码

  4. 避免依赖:测试之间不应有依赖关系

  5. 快速反馈:保持测试快速执行,避免I/O操作

  6. 覆盖率:追求合理的测试覆盖率,但不要盲目追求100%

  7. Mock适度:不要过度使用mock,集成测试也很重要


6. 高级主题

6.1 自定义测试注解

java 复制代码
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@SpringBootTest
@ActiveProfiles("test")
public @interface MyIntegrationTest {
}

然后可以在测试类上使用 @MyIntegrationTest 替代多个注解。

6.2 测试容器支持

使用Testcontainers进行集成测试:

java 复制代码
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.springframework.boot.test.context.SpringBootTest;

@Testcontainers
@SpringBootTest
public class UserRepositoryTestContainersTest {

    @Container
    public static PostgreSQLContainer<?> postgreSQLContainer = 
        new PostgreSQLContainer<>("postgres:13")
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @Test
    public void testWithRealDatabase() {
        // 测试代码
    }
}

6.3 测试Spring Security

java 复制代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@SpringBootTest
@AutoConfigureMockMvc
public class SecuredControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithMockUser(username="admin", roles={"ADMIN"})
    public void testAdminEndpoint() throws Exception {
        mockMvc.perform(get("/api/admin"))
               .andExpect(status().isOk());
    }

    @Test
    @WithMockUser(username="user", roles={"USER"})
    public void testAdminEndpointForbidden() throws Exception {
        mockMvc.perform(get("/api/admin"))
               .andExpect(status().isForbidden());
    }
}

7. 总结

Spring Boot 提供了全面的测试支持,从单元测试到集成测试,从Mock测试到真实环境测试。合理使用这些工具可以大大提高代码质量和开发效率。

记住测试金字塔原则:多写单元测试,适量集成测试,少量端到端测试。

相关推荐
鱟鲥鳚21 分钟前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
大模型码小白2 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司2 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地3 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun3141594 小时前
TCP超时重传机制是为了解决什么问题?
java
莫逸风6 小时前
【AgentScope 2.0】 0. 学习指南
java·llm·agent·agentscope
隔窗听雨眠6 小时前
Spring Boot在云原生时代的编程范式革新研究
spring boot·后端·云原生
z123456789867 小时前
2026最新两款AI编程工具深度对比实测
java·数据库·ai编程
yaoxin5211237 小时前
470. Java 反射 - Member 接口与 AccessFlag
java·开发语言·python
做个文艺程序员7 小时前
Linux第24篇:Java应用监控体系搭建:Prometheus+Grafana可视化运维
java·grafana·prometheus