@MockitoBean 和 @MockitoSpyBean
@MockitoBean and @MockitoSpyBean
@MockitoBean 和 [@MockitoSpyBean](https://docs.spring.io/spring-framework/docs/7.0.3/javadoc-api/org/springframework/test/context/bean/override(mockito/MockitoSpyBean.html) 可以在测试类中使用,分别用 Mockito 的 mock 或 spy 来替换测试环境中的 ApplicationContext 中的 bean。在后一种情况下,原始 bean 的早期实例会被捕获并由 spy 包装起来。
注释可以通过以下方式应用。
- 在测试类或其任何超类中的非静态字段上。
- 在包含
@Nested测试类的类的非静态字段上,或者在@Nested测试类之上的类型层次结构或包含类层次结构中的任何类上。 - 在测试类或其任何超类、实现的接口的类型级别上,这些类位于测试类之上的类型层次结构中。
- 在包含
@Nested测试类的类的类型级别上,或者在@Nested测试类之上的类型层次结构或包含类层次结构中的任何类或接口上。
当在字段上声明@MockitoBean或@MockitoSpyBean时,要模拟或监视的bean将根据被标注字段的类型来推断。如果在ApplicationContext中存在多个符合条件的bean,则可以在该字段上声明一个@Qualifier注解以帮助消除歧义。如果没有@Qualifier注解,那么将被标注字段的名称作为默认限定符使用。另外,你也可以通过设置注解中的value或name属性来显式指定要模拟或监视的bean名称。
当在类型级别声明@MockitoBean或@MockitoSpyBean时,需要通过注解中的types属性来指定要模拟或监控的bean的类型(或多个bean的类型)——例如,@MockitoBean(types = {OrderService.class, UserService.class})。如果在ApplicationContext中存在多个候选bean,你可以通过设置name属性来明确指定要模拟或监控的bean名称。但是请注意,如果配置了明确的bean名称,那么types属性必须只包含一个类型——例如,@MockitoBean(name = "ps1", types = PrintingService.class)。
为了支持模拟配置的复用,可以使用@MockitoBean和@MockitoSpyBean作为元注解来创建自定义的“组合注解”——例如,可以在一个注解中定义通用的模拟或间谍配置,并在整个测试套件中复用该注解。@MockitoBean和@MockitoSpyBean也可以在类型级别上作为可重复使用的注解——例如,可以通过名称来模拟或监视多个bean。
限定符(包括字段的名称)用于确定是否需要创建一个单独的ApplicationContext。如果您使用此功能在多个测试类中模拟或监视同一个bean,请确保字段的命名保持一致,以避免创建不必要的上下文。
将 @MockitoBean 或 @MockitoSpyBean 与 @ContextHierarchy 一起使用可能会导致不良结果,因为默认情况下每个 @MockitoBean 或 @MockitoSpyBean 都会应用于所有的上下文层次结构。为了确保某个特定的 @MockitoBean 或 @MockitoSpyBean 仅应用于单个上下文层次结构,请设置 contextName 属性以匹配已配置的 @ContextConfiguration 名称,例如 @MockitoBean(contextName = "app-config") 或 @MockitoSpyBean(contextName = "app-config")。
有关更多详细信息和示例,请参阅 带有 Bean 重写的上下文层次结构。
每个注释还定义了特定于Mockito的属性,以便微调mocking(模拟)行为。
@MockitoBean 注解使用的是 REPLACE_OR_CREATE bean 重写策略。如果不存在相应的 bean,则会创建一个新的 bean。不过,你可以通过将 enforceOverride 属性设置为 true 来切换到 REPLACE 策略——例如,@MockitoBean(enforceOverride = true)。
@MockitoSpyBean 注解使用了 WRAP 策略,原始实例会被包装成一个 Mockito 间谍(spy)。该策略要求必须存在且仅存在一个符合条件的候选 bean(候选 bean 指的是可以用于替换的 bean)。
正如Mockito的文档中所说明的,有时使用Mockito.when()来模拟spy并不合适——例如,如果对spy调用真实方法会导致不希望出现的副作用。
为了避免这些不希望出现的副作用,可以考虑使用Mockito.doReturn(…) .when(spy)…、Mockito.doThrow(…) .when(spy)…、Mockito.doNothing().when(spy)…以及类似的方法。
当使用@MockitoBean来模拟非单例bean时,该非单例bean将被替换为单例mock,且相应的bean定义也会被转换为singleton类型。因此,如果你模拟的是prototype或带作用域的bean,那么生成的mock也会被视为singleton。
同样地,当使用@MockitoSpyBean来为非单例bean创建spy时,相应的bean定义也会被转换为singleton类型。因此,如果你为prototype或带作用域的bean创建spy,那么生成的spy也会被视为singleton。
当使用@MockitoBean来模拟由FactoryBean创建的bean时,FactoryBean本身会被替换为该FactoryBean所创建对象的单例mock。
同样地,当使用@MockitoSpyBean来为FactoryBean创建spy时,生成的spy实际上是针对FactoryBean所创建的对象,而不是FactoryBean本身。
此外,@MockitoSpyBean不能用于对带作用域的代理(例如,被@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)注解的bean)进行监视。任何此类尝试都会失败并抛出异常。
@MockitoBean 和 @MockitoSpyBean 字段的可见性没有限制。
因此,这些字段可以根据项目的需求或编码习惯设置为 public、protected、包私有(默认可见性)或 private。
@MockitoBean 示例
以下示例展示了如何使用@MockitoBean注解的默认行为。
- Java
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoBean 1
CustomService customService;
// tests...
}
用 Mockito 模拟替换类型为
CustomService的 bean。
在上面的例子中,我们为CustomService创建了一个模拟对象(mock)。如果存在多个同类型的bean,那么将会选择名为customService的bean。否则,测试将会失败,这时你就需要提供一个某种形式的限定符来指定你想要覆盖的是哪个CustomService bean。如果不存在这样的bean,系统会自动生成一个bean名称并创建该bean。
以下示例使用按名称查找(by-name lookup),而不是按类型查找(by-type lookup)。如果不存在名为 service 的 Bean,则会创建一个。
- Java
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoBean("service") 1
CustomService customService;
// tests...
}
用 Mockito 模拟替换名为
service的 bean。
以下 @SharedMocks 注解注册了两个按类型指定的模拟对象(mocks by-type)和一个按名称指定的模拟对象(mock by-name)。
- Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoBean(types = {OrderService.class, UserService.class}) 1
@MockitoBean(name = "ps1", types = PrintingService.class) 2
public @interface SharedMocks {
}
按类型注册
OrderService和UserService的模拟对象。按名称注册
PrintingService的模拟对象。
以下演示了如何在测试类上使用@SharedMocks。
- Java
@SpringJUnitConfig(TestConfig.class)
@SharedMocks 1
class BeanOverrideTests {
@Autowired OrderService orderService; 2
@Autowired UserService userService; 2
@Autowired PrintingService ps1; 2
// 注入其他依赖于这些mock的组件。
@Test
void testThatDependsOnMocks() {
// ...
}
}
通过自定义的
@SharedMods注解来注册常用的mock。可以选择性地注入mock来进行模拟或验证操作。
也可以将mock对象注入到@Configuration类中,或者注入到ApplicationContext中的其他与测试相关的组件中,以便使用Mockito的存根(stub)API来配置这些组件。
@MockitoSpyBean 示例
以下示例展示了如何使用@MockitoSpyBean注解的默认行为。
- Java
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoSpyBean 1
CustomService customService;
// tests...
}
用 Mockito 的 spy 对象包装类型为
CustomService的 bean。
在上面的例子中,我们使用类型为CustomService的bean进行包装。如果存在多个该类型的bean,那么就会选择名为customService的bean。否则,测试将会失败,此时你需要提供某种限定符来指定你想要监视的是哪个CustomService bean。
以下示例使用的是按名称查找(by-name lookup),而不是按类型查找(by-type lookup)。
- Java
@SpringJUnitConfig(TestConfig.class)
class BeanOverrideTests {
@MockitoSpyBean("service") 1
CustomService customService;
// tests...
}
用 Mockito 的 spy 对象包装名为
service的 bean。
以下@SharedSpies注释按类型注册了两个间谍(spy),并按名称注册了一个间谍。
- Java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@MockitoSpyBean(types = {OrderService.class, UserService.class}) 1
@MockitoSpyBean(name = "ps1", types = PrintingService.class) 2
public @interface SharedSpies {
}
按类型注册
OrderService和UserService的间谍(spy)。按名称注册
PrintingService的间谍。
以下演示了如何在测试类上使用@SharedSpies。
- Java
@SpringJUnitConfig(TestConfig.class)
@SharedSpies 1
class BeanOverrideTests {
@Autowired OrderService orderService; 2
Autowired UserService userService; 2
@Autowired PrintingService ps1; 2
// 注入其他依赖于这些“间谍”组件的地方。
@Test
void testThatDependsOnMocks() {
// ...
}
}
通过自定义的
@SharedSpies注解来注册通用的“间谍”组件。可以选择性地注入这些“间谍”组件来进行模拟(stub)或验证(verify)操作。
还可以将这些间谍(spy objects)注入到@Configuration类中,或者ApplicationContext中的其他与测试相关的组件中,以便使用Mockito的存根API(stubbing APIs)来对这些组件进行配置。