在 Spring Boot 测试中使用 @Autowired 和 @InjectMocks

更新于 2025-12-30

Abhinav Pandey 2024-07-11

1. 概述

在本教程中,我们将探讨在 Spring Boot 测试中如何使用 Spring 的 @Autowired 注解和 Mockito 的 @InjectMocks 注解来注入依赖。我们会分析适用这些注解的场景,并通过示例进行说明。

2. 理解测试注解

在开始编写代码示例之前,我们先快速了解一下一些常用测试注解的基础知识。

首先,Mockito 最常用的 @Mock 注解用于为依赖项创建一个模拟(mock)实例,通常与 @InjectMocks 配合使用,后者会将标记为 @Mock 的模拟对象注入到被测试的目标对象中。

除了 Mockito 提供的注解外,Spring Boot 还提供了 @MockBean 注解,可用于创建一个模拟的 Spring Bean。该模拟 Bean 可以被 Spring 上下文中的其他 Bean 使用。此外,如果 Spring 上下文已经自动创建了某些 Bean(无需模拟),我们可以直接使用 @Autowired 注解将其注入。

3. 示例设置

在我们的代码示例中,我们将创建一个包含两个依赖的服务类,然后探索如何使用上述注解对该服务进行测试。

3.1. 依赖项

首先,添加所需的依赖项。我们将引入 Spring Boot Starter Web 和 Spring Boot Starter Test:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>3.3.2</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>3.3.2</version>
    <scope>test</scope>
</dependency>

此外,我们还需要添加 Mockito Core 依赖,用于模拟服务:

<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 的 Service 类型 Bean。

接下来,定义一个依赖于上述服务的服务类:

@Service
public class BookService {
    private DatabaseService databaseService;
    private 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);
    }
}

这个服务类包含一个 getBook() 方法,它使用 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"));
    }
}

首先,我们在测试类上添加 @ExtendWith(MockitoExtension.class) 注解。该扩展允许我们在测试中使用 Mockito 的模拟和注入功能。

接着,我们声明 DatabaseServiceObjectMapper 字段,并用 @Mock 注解标记,从而创建它们的模拟对象。同时,我们在待测的 BookService 实例上使用 @InjectMocks 注解,这样 Mockito 会自动将前面声明的模拟依赖注入到该服务中。

最后,在测试方法中,我们为模拟对象定义行为,并调用 getBook() 方法进行验证。

注意:使用此方法时,必须模拟服务的所有依赖。例如,如果不模拟 ObjectMapper,在调用 writeValueAsString() 时会抛出 NullPointerException

4.2. 使用 @Autowired@MockBean

在上述方法中,我们模拟了所有依赖。但在某些情况下,我们可能只想模拟部分依赖(比如只模拟 DatabaseService),而保留其他依赖(如 ObjectMapper)的真实实现。

由于此时需要加载 Spring 上下文,我们可以结合使用 @Autowired@MockBean

@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"));
    }
}

首先,为了使用 Spring 上下文中的 Bean,我们需要在测试类上添加 @SpringBootTest 注解。接着,使用 @MockBean 标记 DatabaseService,这样 Spring 会用模拟对象替换掉原本的 DatabaseService Bean。而 BookService 则通过 @Autowired 从应用上下文中注入。

此时,BookService 中的 DatabaseService 会被替换成模拟对象,而 ObjectMapper 仍使用 Spring 容器中实际创建的 Bean。

这种方式的优势在于:无需为 ObjectMapper 模拟行为,适合在集成测试中仅对部分组件进行模拟。

4.3. 同时使用 @Autowired@InjectMocks

我们也可以在上述场景中使用 @InjectMocks 而非 @MockBean。来看一下具体实现:

@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"));
}

这里,我们使用 @Mock(而非 @MockBean)来模拟 DatabaseService。同时,在 BookService 上同时使用 @Autowired@InjectMocks

需要注意的是:当这两个注解一起使用时,@InjectMocks 不会自动注入模拟依赖。Spring 会先通过 @Autowired 注入真实的 BookService 实例。

但我们可以在测试中调用 MockitoAnnotations.openMocks(this) 方法,手动触发 Mockito 的注入逻辑。该方法会查找带有 @InjectMocks 的字段,并将标记为 @Mock 的对象注入进去。

我们在测试方法中、模拟行为定义之前调用该方法。这种方式适用于需要动态决定何时使用模拟对象、何时使用真实 Bean 的复杂测试场景。

5. 方法对比

现在我们已经了解了多种测试方法,下面对它们进行总结比较:

方法 描述 适用场景
@Mock + @InjectMocks 使用 Mockito 的 @Mock 创建依赖的模拟实例,并通过 @InjectMocks 注入到被测对象中。 适用于单元测试,希望完全控制并模拟被测类的所有依赖。
@MockBean + @Autowired 使用 Spring Boot 的 @MockBean 创建模拟的 Spring Bean,并通过 @Autowired 注入其他真实 Bean。 适用于集成测试,只需模拟部分 Spring Bean,其余依赖保持真实。
@InjectMocks + @Autowired 使用 Mockito 的 @Mock 创建模拟对象,并通过 @InjectMocks 注入到已由 Spring 自动装配的 Bean 中。 提供灵活性,适用于需要临时覆盖 Spring 注入的 Bean 的复杂测试场景。

6. 结论

本文介绍了 Mockito 与 Spring Boot 中常用测试注解(@Mock@InjectMocks@Autowired@MockBean)的不同使用方式。我们探讨了根据测试需求选择合适注解组合的策略:

  • 纯单元测试 → 使用 @Mock + @InjectMocks
  • Spring 集成测试 → 使用 @MockBean + @Autowired
  • 动态/混合测试 → 使用 @Mock + @Autowired + @InjectMocks + MockitoAnnotations.openMocks()

合理选择这些注解,可以显著提升测试的可维护性、可读性和可靠性。