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 {
@Test
void 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 {
@Test
void 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 {
@Autowired
private TestRestTemplate restTemplate;
@Test
public 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 {
@Autowired
private MockMvc mockMvc;
@Test
public void testHello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("Hello, World!"));
}
}
4.测试配置
使用@TestConfiguration
有时候在测试中需要提供特定的bean实现,可以使用@TestConfiguration注解来定义额外的配置
@TestConfiguration
public class TestConfig {
@Bean
public MyService myService() {
return new MyService() {
// 自定义实现
};
}
}
5.模拟(Mocking)
使用@MockBean和@SypBean来模拟Spring容器中的bean
-
@MockBean:创建一个Mock对象并替换Spring容器中该类型的bean.
-
@SpyBean:创建一个Spy对象,会调用真实方法,除非被stub.
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@MockBean
private UserRepository userRepository;
@Test
public 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 {
@Autowired
private MockMvc mockMvc;
@Test
public 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 {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
public 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 {
@Autowired
MockMvc mockMvc;
@MockBean // 关键:模拟Service避免空指针
UserService userService;
@Test
void 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 {
@Autowired
TestRestTemplate restTemplate; // 自动处理URL
@MockBean // 仍需要Mock以避免真实依赖
UserService userService;
@Test
void 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 TD A[测试类] -->|@Autowired| B[MockMvc] A -->|@MockBean| C[UserService] D[UserController] -->|@Autowired| C B -->|调用| D
特殊场景处理
-
Lazy初始化导致空指针
@MockBean(lazyInit = true) // 延迟初始化避免早期依赖问题 ExternalService externalService;
-
Bean冲突
@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
-
自定义配置
@TestConfiguration
static class TestConfig {
@Bean
@Primary
UserService 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复制资源
原文地址:https://blog.csdn.net/qq_39594281/article/details/148677545
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!
