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

2025/03/15

1. 概述

在本教程中,我们将探讨在Spring Boot测试中注入依赖项时Spring Boot的@Autowired和Mockito的@InjectMocks的用法,我们将讨论需要使用它们的用例并查看示例。

2. 理解测试注解

在开始代码示例之前,让我们快速了解一些测试注解的基础知识。

首先,Mockito中最常用的@Mock注解用于创建依赖项的Mock实例以供测试,它通常与@InjectMocks结合使用,将标有@Mock的Mock注入到被测试的目标对象中

除了Mockito的注解之外,Spring Boot的注解@MockBean可以帮助创建Mock的Spring bean,然后,上下文中的其他Bean可以使用Mock的Bean。此外,如果Spring上下文自行创建了无需Mock即可使用的Bean,我们可以使用@Autowired注解来注入它们

3. 示例设置

在我们的代码示例中,我们将创建一个具有两个依赖项的服务,然后我们将探索使用上述注解来测试该服务。

3.1 依赖项

让我们首先添加所需的依赖项,包括Spring Boot Starter WebSpring 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>

除此之外,我们还将添加Mock服务所需的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;
    
    // constructor, setters/getters
}

3.3 Service

接下来看看我们的Service,首先,我们定义一个负责数据库交互的服务:

@Service
public class DatabaseService {
    public Book findById(String id) {
        // querying a Database and getting a book
        return new Book("id","Name", "Author");
    }
}

我们不会深入讨论数据库交互,因为它们与示例无关。我们使用@Service注解将类声明为Service构造型的Spring 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 API将Book对象转换并返回为JSON字符串。

因此,该服务有两个依赖:DatabaseService和ObjectMapper。

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)标注测试类,MockitoExtension扩展允许我们在测试中Mock和注入对象。

接下来,我们声明DatabaseService和ObjectMapper字段并用@Mock标注它们,这会为它们两者创建Mock对象。在声明要测试的BookService实例时,我们添加了@InjectMocks注解,这会注入服务所需的任何依赖,这些依赖之前已用@Mocks声明过

最后,在我们的测试中,我们模拟Mock对象的行为并测试我们服务的getBook()方法。

使用此方法时,必须Mock服务的所有依赖项。例如,如果我们不Mock ObjectMapper,则在测试方法中调用它时会导致NullPointerException。

4.2 使用@Autowired和@MockBean

在上述方法中,我们Mock了两个依赖项。但是,可能需要Mock某些依赖项而不Mock其他依赖项。假设我们不需要Mock ObjectMapper的行为,而仅Mock DatabaseService。

由于我们在测试中加载了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。然后我们使用@Autowired从应用程序上下文中获取BookService实例。

当BookService Bean被注入时,实际的DatabaseService Bean将被Mock的Bean替换。相反,ObjectMapper Bean仍然与应用程序最初创建的相同。

当我们现在测试这个实例时,我们不需要Mock ObjectMapper的任何行为。

当我们需要测试嵌套Bean的行为并且不想Mock每个依赖项时,此方法很有用。

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来Mock DatabaseService。除了@Autowired,我们还将@InjectMocks注解添加到BookService实例。

当两个注解一起使用时,@InjectMocks不会自动注入Mock依赖项,并且会在测试开始时注入自动注入的BookService对象。

但是,我们可以稍后在测试中通过调用MockitoAnnotations.openMocks()方法来注入DatabaseService的Mock实例。此方法查找标有@InjectMocks的字段并将Mock对象注入其中。

在我们需要Mock DatabaseService的行为之前,我们在测试中调用它。当我们想要动态决定何时使用Mock以及何时使用实际Bean来获取依赖项时,此方法很有用。

5. 方法比较

现在我们已经研究了多种方法,让我们总结一下它们之间的比较:

方法 描述 用法
@Mock与@InjectMocks 使用Mockito的@Mock注解创建依赖项的Mock实例,并使用@InjectMocks将这些Mock注入到正在测试的目标对象中 适用于单元测试,我们想要Mock被测试类的所有依赖项
@MockBean与@Autowired 利用Spring Boot的@MockBean注解创建一个Mock的Spring Bean并使用@Autowired注入这些Bean 非常适合在Spring Boot应用程序中进行集成测试,它允许Mock一些Spring Bean,同时从Spring的依赖注入中获取其他Bean
@InjectMocks与@Autowired 使用Mockito的@Mock注解创建Mock实例,并使用@InjectMocks将这些Mock注入到已使用Spring自动注入的目标Bean中 在需要使用Mockito临时Mock某些依赖项以覆盖注入的Spring Bean的情况下,提供了灵活性。适用于在Spring应用程序中测试复杂场景

6. 总结

在本文中,我们研究了Mockito和Spring Boot注解的不同用例-@Mock、@InjectMocks、@Autowired和@MockBean,我们探讨了何时根据测试需求使用注解的不同组合。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章