自学内容网 自学内容网

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启动整个上下文,并使用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 {
​
    @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);
    }
}

调试步骤

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


原文地址:https://blog.csdn.net/qq_39594281/article/details/148677545

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!