1. 概述

本文将深入探讨在 Spring Boot 测试中使用 @Autowired 和 Mockito 的 @InjectMocks 注解进行依赖注入的最佳实践。我们将分析需要使用这些注解的场景,并通过实际示例演示具体用法。

2. 理解测试注解

在开始代码示例前,先快速梳理几个核心测试注解的基础知识:

Mockito 的 @Mock
创建依赖项的模拟实例,常与 @InjectMocks 配合使用,后者会将标记为 @Mock 的模拟对象注入到被测试的目标对象中。

Spring Boot 的 @MockBean
创建模拟的 Spring Bean,该模拟 Bean 可被上下文中的其他 Bean 使用。当 Spring 上下文自动创建的 Bean 无需模拟时,可直接使用 @Autowired 注入。

3. 示例搭建

我们将创建一个包含两个依赖项的服务,并演示如何使用上述注解进行测试。

3.1. 依赖配置

添加必要的 Maven 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.2.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>3.2.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>5.12.0</version>
</dependency>

3.2. DTO 定义

创建服务层使用的 DTO:

public class Book {
    private String id;
    private String name;
    private String author;
    
    // 构造方法、getter/setter
}

3.3. 服务实现

首先定义数据库交互服务:

@Service
public class DatabaseService {
    public Book findById(String id) {
        // 数据库查询逻辑(示例简化)
        return new Book("id","Name", "Author");
    }
}

⚠️ 注意:数据库交互细节与示例无关,使用 @Service 声明为 Spring Bean。

接着创建依赖上述服务的业务服务:

@Service
public class BookService {
    private final DatabaseService databaseService;
    private final ObjectMapper objectMapper;

    BookService(DatabaseService databaseService, ObjectMapper objectMapper) {
        this.databaseService = databaseService;
        this.objectMapper = objectMapper;
    }

    String getBook(String id) throws JsonProcessingException {
        Book book = databaseService.findById(id);
        return objectMapper.writeValueAsString(book);
    }
}

该服务通过 DatabaseService 获取书籍数据,并使用 Jackson 的 ObjectMapper 将对象转为 JSON 字符串。核心依赖:DatabaseServiceObjectMapper

4. 测试实现

现在演示测试 BookService 的多种注解组合方案。

4.1. 使用 @Mock 和 @InjectMocks

通过 @Mock 模拟所有依赖,并用 @InjectMocks 注入:

@ExtendWith(MockitoExtension.class)
class BookServiceMockAndInjectMocksUnitTest {
    @Mock
    private DatabaseService databaseService;

    @Mock
    private ObjectMapper objectMapper;

    @InjectMocks
    private BookService bookService;

    @Test
    void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
        Book book1 = new Book("1234", "Inferno", "Dan Brown");
        when(databaseService.findById(eq("1234"))).thenReturn(book1);

        when(objectMapper.writeValueAsString(any())).thenReturn(new ObjectMapper().writeValueAsString(book1));

        String bookString1 = bookService.getBook("1234");
        Assertions.assertTrue(bookString1.contains("Dan Brown"));
    }
}

关键点:

  1. @ExtendWith(MockitoExtension.class) 启用 Mockito 扩展
  2. @Mock 创建模拟对象
  3. @InjectMocks 将模拟对象注入目标服务

踩坑提醒:必须模拟所有依赖!未模拟 ObjectMapper 会导致测试时抛出 NullPointerException

4.2. 使用 @Autowired 和 @MockBean

当只需模拟部分依赖时(如仅模拟 DatabaseService):

@SpringBootTest
class BookServiceAutowiredAndInjectMocksUnitTest {
    @MockBean
    private DatabaseService databaseService;

    @Autowired
    private BookService bookService;

    @Test
    void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
        Book book1 = new Book("1234", "Inferno", "Dan Brown");
        when(databaseService.findById(eq("1234"))).thenReturn(book1);

        String bookString1 = bookService.getBook("1234");
        Assertions.assertTrue(bookString1.contains("Dan Brown"));
    }
}

核心机制:

  • @SpringBootTest 加载 Spring 上下文
  • @MockBean 替换上下文中的真实 Bean
  • @Autowired 注入真实 BookService(其 ObjectMapper 依赖保持真实)

✅ 优势:无需模拟所有依赖,适合测试嵌套 Bean 的真实行为。

4.3. 混合使用 @Autowired 和 @InjectMocks

动态控制依赖模拟的进阶方案:

@SpringBootTest
class BookServiceAutowiredAndInjectMocksUnitTest {
    @Mock
    private DatabaseService databaseService;

    @Autowired
    @InjectMocks
    private BookService bookService;

    @Test
    void givenBookService_whenGettingBook_thenBookIsCorrect() throws JsonProcessingException {
        Book book1 = new Book("1234", "Inferno", "Dan Brown");

        MockitoAnnotations.openMocks(this); // 关键步骤!

        when(databaseService.findById(eq("1234"))).thenReturn(book1);
        String bookString1 = bookService.getBook("1234");
        Assertions.assertTrue(bookString1.contains("Dan Brown"));
    }
}

执行流程:

  1. @Autowired 注入真实 BookService
  2. @Mock 创建模拟对象
  3. MockitoAnnotations.openMocks(this) 手动触发模拟对象注入

⚠️ 注意:必须显式调用 openMocks(),否则模拟对象不会生效!

5. 方案对比

方案组合 描述 适用场景
@Mock + @InjectMocks 纯 Mockito 模拟所有依赖 单元测试(隔离被测类)
@MockBean + @Autowired Spring Boot 模拟部分 Bean 集成测试(混合真实/模拟依赖)
@Autowired + @InjectMocks 手动控制模拟注入时机 复杂场景测试(动态切换依赖)

6. 总结

本文系统分析了 Spring Boot 测试中 @Autowired@Mock@InjectMocks@MockBean 的协作机制。根据测试需求选择合适组合:

  • 单元测试:优先用 @Mock + @InjectMocks
  • 集成测试:优先用 @MockBean + @Autowired
  • 复杂场景:考虑混合方案动态控制模拟

完整示例代码请参考 GitHub 仓库


原始标题:Using @Autowired and @InjectMocks in Spring Boot Tests | Baeldung