当前位置: 首页 > java >正文

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启动整个上下文,并使用TestRestTemplateMockMvc进行测试

使用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);}
}

调试步骤

  1. 检查测试类是否有@SpringBootTest@WebMvcTest

  2. 确认是否添加了@MockBean@SpyBean用于Service

  3. 在测试方法开头添加调试:

@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复制资源

http://www.xdnf.cn/news/14414.html

相关文章:

  • AI编程:正在拉开新一轮“人与人”的差距
  • Kafka多副本机制
  • python 将字典的值替换为键名作为变量名的形式(带缩进)
  • 基于51单片机的直流电动控制速度系统proteus仿真
  • leetcode 分割回文串 java
  • 总结用ubuntu一直以来遇到的问题
  • 加盐加密算法
  • 浏览器基础及缓存
  • 【Linux】Linux 信号驱动I/O
  • Git 配置 SSH 密钥与私钥教程(跨平台完整指南)
  • 京东API接口最新指南:店铺所有商品接口的接入与使用
  • 易语言模拟真人鼠标轨迹算法 - 非贝塞尔曲线
  • 大模型的开发应用(十一):对话风格微调项目(下):微调与部署
  • 《AI辅助编程:从零掌握核心逻辑》工作坊开业
  • mysql修改密码笔记
  • 基于51单片机的智能小车:按键调速、障碍跟踪、红外循迹与数码管显示(一个合格的单片机课设)
  • 浙江康冠锁业携智能锁具亮相2025上海国际快递物流展
  • 山东大学软件学院创新项目实训开发日志——第十七周(二)
  • 【C语言扩展识别实数负数】2022-5-29
  • Web第二次方向考核复盘
  • OpenHarmony 5.0读取文件并写入到另一份文件(公共文件夹),并保持原先的格式以及编码类型
  • 论文略读:Does Refusal Training in LLMs Generalize to the Past Tense?
  • Hierarchical Vector Quantization for Unsupervised Action Segmentation
  • 介质访问控制——随机访问控制
  • Java的DI依赖注入
  • 2025如何快速给人物模型添加骨骼
  • 【Python机器学习(一)】NumPy/Pandas手搓决策树+使用Graphviz可视化(以西瓜书数据集为例)
  • 【深度剖析】领信卓越:福耀玻璃的数字化转型(上篇2:转型动机分析)
  • 嵌入式知识篇---三种坐标系
  • 揭开肾细胞的分子密码:当 METTL3 遇上 FOSL1【AbMole】