springboot测试类原理
使用trea编写自动测试类,发现不能执行,之前虽然看过测试类相关的内容,但是也只大概知道是怎么样的,对测试类代码和原理等没有很清晰的概念,只记得使用SpringbootTest注解,通过快捷键对每个接口,方法生成测试代码,通过assert断言判断结果是否符合预期,可以写一个类似整体的测试方法, 一点击便会执行所有的测试方式,通过mockMvc模拟一个http请求等。
1.测试依赖
首先,需要在项目中添加测试依赖。Spring Boot提供了一个名为spring-boot-starter-test
的启动器,它包含了常用的测试库,如JUnit Jupiter、Spring Test、AssertJ、Hamcrest、Mockito等。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> </dependency>
2.测试类的基本结构
一个典型的Spring Boot测试类如下所示:
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MyApplicationTests { @Testvoid contextLoads() {} }
-
@SpringBootTest
: 这个注解用于告诉Spring Boot这是一个集成测试,它会加载整个应用程序上下文。默认情况下,它会从当前包及其子包中搜索主配置类(例如带有@SpringBootApplication
的类)并启动。
3.单元测试与集成测试
在Spring Boot中,测试可以分为两种:
-
单元测试:测试单个组件(如一个Service类),通常使用Mockito来模拟依赖
-
集成测试:测试多个组件的交互,甚至包括整个应用程序的启动。
单元测试示例:
假设有一个CalcuatorService类:
@Service public class CalculatorService {public int add(int a, int b) {return a + b;} }
对它的单元测试可以这样写:
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; class CalculatorServiceTest { @Testvoid testAdd() {CalculatorService calculatorService = new CalculatorService();int result = calculatorService.add(2, 3);assertEquals(5, result);} }
集成测试示例
如果你需要测试一个控制器(Controller)与服务的整合,可以使用@SpringBootTest
启动整个上下文,并使用TestRestTemplate
或MockMvc
进行测试
使用TestRestTemplate
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.ResponseEntity; import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HelloControllerIntegrationTest { @Autowiredprivate TestRestTemplate restTemplate; @Testpublic void testHello() {ResponseEntity<String> response = restTemplate.getForEntity("/hello", String.class);assertEquals("Hello, World!", response.getBody());} }
注意:webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
表示在随机端口启动服务器,避免端口冲突。
使用MockMvc
如果不想启动整个服务器,而只是想测试Controller层的逻辑,可以使用MockMvc:
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.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @AutoConfigureMockMvc public class HelloControllerMockMvcTest { @Autowiredprivate MockMvc mockMvc; @Testpublic void testHello() throws Exception {mockMvc.perform(get("/hello")).andExpect(status().isOk()).andExpect(content().string("Hello, World!"));} }
4.测试配置
使用@TestConfiguration
有时候在测试中需要提供特定的bean实现,可以使用@TestConfiguration
注解来定义额外的配置
@TestConfiguration public class TestConfig {@Beanpublic MyService myService() {return new MyService() {// 自定义实现};} }
5.模拟(Mocking)
使用@MockBean和@SypBean来模拟Spring容器中的bean
-
@MockBean:创建一个Mock对象并替换Spring容器中该类型的bean.
-
@SpyBean:创建一个Spy对象,会调用真实方法,除非被stub.
@SpringBootTest public class UserServiceTest { @Autowiredprivate UserService userService; @MockBeanprivate UserRepository userRepository; @Testpublic void testGetUserById() {// 模拟userRepository的行为when(userRepository.findById(1L)).thenReturn(new User(1L, "Alice")); User user = userService.getUserById(1L);assertEquals("Alice", user.getName());} }
6.测试事务
默认情况下,Spring Boot的测试是事务性的,即每个测试方法执行后都会回滚事务。可以使用@Transactional
和@Rollback
来控制。
7.使用@TestPropertySource
为了测试,可能需要覆盖一些配置属性,可以使用@TestPropertySource注解
@SpringBootTest @TestPropertySource(properties = {"my.property=value"}) public class PropertyTest {// ... }
8.使用@SpringBootTest的classes属性
如果测试类不在主应用程序类的包或其子包下,需要显示指定主配置类。
@SpringBootTest(classes = {Application.class}) public class MyTest {// ... }
9.使用@WebMvcTest进行Controller层单元测试
如果只想测试Controller层,可以使用@WebMvcTest。它会自动配置MockMvc并只加载相关的Controller和配置。
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.web.servlet.MockMvc;@WebMvcTest(HelloController.class) public class WebMvcTestExample {@Autowiredprivate MockMvc mockMvc;@Testpublic void testHello() throws Exception {mockMvc.perform(get("/hello")).andExpect(status().isOk()).andExpect(content().string("Hello, World!"));} }
10.使用@DataJpaTest进行JPA测试
@DataJpaTest用于测试JPA相关的组件,它会配置一个内存数据(如H2)和自动配置JPA相关的配置,只加载与JPA相关的组件。
@DataJpaTest public class UserRepositoryTest {@Autowiredprivate TestEntityManager entityManager;@Autowiredprivate UserRepository userRepository;@Testpublic void testFindByEmail() {User user = new User("test@example.com", "password");entityManager.persist(user);User found = userRepository.findByEmail("test@example.com");assertEquals(user.getEmail(), found.getEmail());} }
11.测试启动性能优化
由于@SpringBootTest会加载整个应用程序上下文,如果测试很多,启动速度可能会变慢。为了优化:
-
尽量使用切片测试(如
@WebMvcTest
,@DataJpaTest
)来减少加载的组件。 -
使用
@MockBean
来模拟不需要的依赖。
12.Spring Boot测试中Service空指针问题原因及解决
在Spring Boot测试中调用控制器接口时Service出现空指针异常,通常是由于测试环境未正确初始化Spring容器导致依赖注入失败。以下是根本原因和解决方案
问题根源分析
1.容器未启动:
-
测试类缺少@SpringBootTest或有效注解
-
未启用Spring测试支持(JUnit5需@ExtendWith(SpringExtension.class))
2.依赖未注入:
-
控制器中的@Autowired Service 未成功注入
-
测试类直接创建控制器实例而非从容器获取
3.作用域问题:
-
使用了new Controller() 而非注入的Bean
-
未使用@MockBean处理依赖
方案一:使用@WebMvcTest进行切片测试(推荐)
@WebMvcTest(UserController.class) // 只加载Web层组件 class UserControllerTest {@AutowiredMockMvc mockMvc;@MockBean // 关键:模拟Service避免空指针UserService userService; @Testvoid getUser_shouldReturn200() throws Exception {// 设置Mock行为when(userService.findById(anyLong())).thenReturn(new User(1, "Test User"));// 模拟HTTP请求mockMvc.perform(get("/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Test User"));} }
方案二:完整上下文测试(@SpringBootTest)
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) class IntegrationTest {@AutowiredTestRestTemplate restTemplate; // 自动处理URL@MockBean // 仍需要Mock以避免真实依赖UserService userService;@Testvoid testUserEndpoint() {when(userService.findById(1L)).thenReturn(new User(1, "Mock User"));ResponseEntity<User> response = restTemplate.getForEntity("/users/1", User.class);assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);assertThat(response.getBody().getName()).isEqualTo("Mock User");} }
关键注意事项:
1.依赖模拟原则:
//正确方式-通过容器注入 @Autowired private UserController controller;//从容器获取//错误方式-导致空指针 private UserController controller = new UserController();
2.层级关系图
graph TDA[测试类] -->|@Autowired| B[MockMvc]A -->|@MockBean| C[UserService]D[UserController] -->|@Autowired| CB -->|调用| D
特殊场景处理
-
Lazy初始化导致空指针
@MockBean(lazyInit = true) // 延迟初始化避免早期依赖问题 ExternalService externalService;
-
Bean冲突
@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
-
自定义配置
@TestConfiguration static class TestConfig {@Bean@PrimaryUserService testUserService() {return mock(UserService.class);} }
调试步骤
-
检查测试类是否有
@SpringBootTest
或@WebMvcTest
-
确认是否添加了
@MockBean
或@SpyBean
用于Service -
在测试方法开头添加调试:
@BeforeEach void setup(ApplicationContext context) {// 检查Controller是否被容器管理assertThat(context.getBean(UserController.class)).isNotNull(); // 检查Service是否被注入assertThat(context.getBean(UserController.class).getService()).isNotNull(); }
4.检查控制器是否使用@Autowired
注入Service
src/main/java → 源码目录 src/main/resources → 资源目录 src/test/java → 测试目录
-
IDEA的目录标记仅用于IDE内部优化,Maven构建时完全依赖
pom.xml
和标准目录结构,忽略IDE标记 -
若手动创建非标准目录(如
src/config
),需在pom.xml
中显式配置资源路径:
<build><resources><resource><directory>src/config</directory></resource></resources> </build>
2.idea MarkDirectory as说明
使用trea编译器生成测试代码,由于该项目之前没写过测试代码,都是通过postman进行测试,所以新建了测试类的文件夹以及目录,然后到idea中进行测试,发现新增的类没有被识别,无法编译,通过 idea的MarkDirectory as Test Sources Root功能解决问题。
一,目录标记的核心功能
目的:明确目录在项目中的角色,控制其参与编译,索引和资源处理的行为
二:各目录类型详解
1.Source Root(源码根目录)
-
标识:目录图标变为蓝色(
src/main/java
典型示例)。 -
作用:存放项目核心源码,目录内代码会被编译并加入classpath
-
使用场景: src/main/java等核心代码目录
2.Test Sources Root(测试源码目录)
-
标识:目录图标变为绿色(
src/test/java
典型示例)。 -
作用:
-
-
存放单元测试、集成测试代码。
-
测试代码与生产代码分离,编译输出到独立目录2**5**。
-
-
使用场景:JUnit 测试类存放目录。
3.Resources Root(资源根目录)
-
作用:存放配置文件(如application.yml),静态资源(图片,模板)
-
构建时自动复制内容到输出目录(如target/classes)
-
使用场景:SpringBoot项目的配置文件目录
4.Test Resources Root(测试资源根目录)
-
标识:与Resource Root类型,但专用于测试
5.Exluded(排查目录)
-
作用:
-
停止索引与搜索:IDE 不索引内容,全局搜索不包含该目录1。
-
禁止编译:不参与编译构建,不加入 classpath1**2**。
-
取消智能提示:内部文件无代码补全、错误检查等功能。
-
使用场景:
-
临时文件(
/tmp
)、编译输出(/target
)、日志目录。 -
第三方库或自动生成代码(如
node_modules
) -
取消排除: File → Project Structure → Modules → Sources,选中目录后点击 Cancel Exclusion
idea的MarkDirectory as 和maven打包有关系吗
构建时以Maven配置为准
-
Maven遵循约定优于配置原则,默认目录结构为:
src/main/java → 源码目录 src/main/resources → 资源目录 src/test/java → 测试目录
-
IDEA的目录标记仅用于IDE内部优化,Maven构建时完全依赖
pom.xml
和标准目录结构,忽略IDE标记 -
若手动创建非标准目录(如
src/config
),需在pom.xml
中显式配置资源路径:
<build><resources><resource><directory>src/config</directory></resource></resources> </build>
-
潜在冲突场景
-
目录标记错误:若将src/main/java误标记为Excluded,IDEA会停止代码提示,但Maven仍正常编译(因Maven不读取IDE配置)
-
资源未复制:若未标记
Resources Root
,IDEA可能无法实时预览资源文件,但Maven构建时会按pom.xml
复制资源