1. 概述

在这个教程中,我们将讨论如何使用依赖注入将Mockito模拟对象插入Spring Bean进行单元测试。在实际应用中,当组件经常需要访问外部系统时,提供适当的隔离测试至关重要,这样我们就能专注于测试单个单元的功能,而无需在每次测试中都牵涉到整个类层次结构。

注入mock是实现这种隔离的一种清晰方式。

2. Maven 依赖

为了单元测试和模拟对象,我们需要以下Maven依赖:

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

我们在这个示例中选择了Spring Boot,但经典的Spring同样适用。

3. 编写测试

3.1. 业务逻辑

首先,创建一个我们将要测试的简单服务:

@Service
public class NameService {
    public String getUserName(String id) {
        return "Real user name";
    }
}

然后将其注入到UserService类中:

@Service
public class UserService {

    private NameService nameService;

    @Autowired
    public UserService(NameService nameService) {
        this.nameService = nameService;
    }

    public String getUserName(String id) {
        return nameService.getUserName(id);
    }
}

对于这篇文章,提供的类无论提供什么id,都会返回一个名字。这样做是为了避免测试复杂逻辑而分心。

我们还需要一个标准的Spring Boot主类,用于扫描bean并初始化应用程序:

@SpringBootApplication
public class MocksApplication {
    public static void main(String[] args) {
        SpringApplication.run(MocksApplication.class, args);
    }
}

3.2. 测试逻辑

现在转向测试逻辑。首先,我们必须配置测试的应用上下文:

@Profile("test")
@Configuration
public class NameServiceTestConfiguration {
    @Bean
    @Primary
    public NameService nameService() {
        return Mockito.mock(NameService.class);
    }
}

@Profile注解告诉Spring只有在“test”配置活跃时才应用此配置。@Primary注解确保在自动装配时使用这个实例而不是真实实例。方法本身创建并返回我们的NameService类的Mockito模拟。

接下来我们可以编写单元测试:

@ActiveProfiles("test")
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MocksApplication.class)
public class UserServiceUnitTest {

    @Autowired
    private UserService userService;

    @Autowired
    private NameService nameService;

    @Test
    public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {
        Mockito.when(nameService.getUserName("SomeId")).thenReturn("Mock user name");
        String testName = userService.getUserName("SomeId");
        Assert.assertEquals("Mock user name", testName);
    }
}

我们使用@ActiveProfiles注解启用“test”配置,并激活我们之前编写的模拟配置。结果,Spring为UserService类自动装配一个真实实例,但对于NameService类则是模拟对象。测试本身是一个典型的JUnit+Mockito测试。我们配置模拟对象的行为,然后调用我们想要测试的方法,并断言其返回我们期望的值。

也可以(尽管不推荐)避免在这样的测试中使用环境配置。要做到这一点,可以移除@Profile@ActiveProfiles注解,并在UserServiceTest类上添加@ContextConfiguration(classes = NameServiceTestConfiguration.class)注解。

4. 总结

在这篇简短的文章中,我们学习了如何轻松地将Mockito模拟对象注入到Spring Bean中。

如往常一样,所有代码示例可在GitHub上找到。