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, "[email protected]", "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("[email protected]", "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("[email protected]", "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, "[email protected]", "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测试到真实环境测试。合理使用这些工具可以大大提高代码质量和开发效率。

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

相关推荐
blammmp13 分钟前
算法专题四:前缀和
java·开发语言·算法
饕餮争锋21 分钟前
Spring普通配置类 vs 自动配置类-笔记
java·笔记·spring
Aimyon_3634 分钟前
Java复习笔记-基础
java·开发语言·笔记
望未来无悔43 分钟前
系统学习算法:动态规划(斐波那契+路径问题)
java·算法
琢磨先生David1 小时前
Java 企业级开发设计模式全解析
java·设计模式
天上掉下来个程小白1 小时前
缓存菜品-04.功能测试
java·spring boot·缓存·微信小程序·需求分析·苍穹外卖
Xiaohong07161 小时前
工程管理系统简介 工程管理系统源码 java工程管理系统 工程管理系统功能设计 从架构到实操
java·工程管理系统源码·企业工程管理系统源码
不当菜虚困1 小时前
JAVA设计模式——(十一)建造者模式(Builder Pattern)
java·设计模式·建造者模式
codefly-xtl1 小时前
责任链设计模式
java·开发语言·设计模式
forestsea1 小时前
Maven 动态版本与SNAPSHOT机制详解
java·maven