java - Mockito:如何模拟注入对象没有无参数构造函数的弹簧特殊DI
问题描述
我Mockito 3.4.6
在单元测试中使用,实际上,我已经将 Mockito 集成到我的单元测试中并且效果很好。虽然,现在我需要优化一些单元测试,但它是一种特殊的依赖注入,注入的对象没有无参数构造函数,我试过@Spy
但没有奏效。
我的测试:我试过 1. @Spy
; 2.@Spy
使用设置实例= getDtInsightApi()
;3.@Spy
有@InjectMocks
,所有的测试都失败了。正如 Mockito 文档所说,它似乎不适用于这种情况。
@InjectMocks Mockito 将尝试仅通过构造函数注入、setter 注入或属性注入按顺序注入模拟,如下所述。
此外,如果只使用@Spy
,它会抛出MockitoException
:
org.mockito.exceptions.base.MockitoException:
Failed to release mocks
This should not happen unless you are using a third-part mock maker
...
Caused by: org.mockito.exceptions.base.MockitoException: Unable to initialize @Spy annotated field 'api'.
Please ensure that the type 'DtInsightApi' has a no-arg constructor.
...
Caused by: org.mockito.exceptions.base.MockitoException: Please ensure that the type 'DtInsightApi' has a no-arg constructor.
请参阅我的伪代码如下:
配置类:
@Configuration
public class SdkConfig {
@Resource
private EnvironmentContext environmentContext;
@Bean(name = "api")
public DtInsightApi getApi() {
DtInsightApi.ApiBuilder builder = new DtInsightApi.ApiBuilder()
.setServerUrls("sdkUrls")
return builder.buildApi();
}
}
DtInsightApi
没有公共无参数构造函数的类,并通过其内部类获取实例
public class DtInsightApi {
private String[] serverUrls;
DtInsightApi(String[] serverUrls) {
this.serverUrls = serverUrls;
}
// inner class
public static class ApiBuilder {
String[] serverUrls;
public ApiBuilder() {
}
...code...
public DtInsightApi buildApi() {
return new DtInsightApi(this.serverUrls);
}
}
...code...
}
单元测试类:
public Test{
@Autowired
private PendingTestService service;
@Spy
private Api api = getDtInsightApi();
@Mock
private MockService mockService;
@Before
public void setUp() throws Exception {
// open mock
MockitoAnnotations.openMocks(this);
// i use doReturn(...).when() for @Spy object
Mockito.doReturn(mockService).when(api)
.getSlbApiClient(MockService.class);
Mockito.when(mockService.addOrUpdate(any(MockDTO.class)))
.thenReturn(BaseObject.getApiResponseWithSuccess());
}
public DtInsightApi getDtInsightApi () {
return new DtInsightApi.ApiBuilder()
.setServerUrls(new String[]{"localhost:8080"})
.buildApi();
}
@Test
public void testUpdate() {
service.update();
}
}
PendingTestService
:
@Service
public class PendingTestService{
@Autowired
DtInsightApi api;
public void update() {
// here mockService isn't the object i mocked
MockService mockService = api.getSlbApiClient(MockService.class);
mockService.update();
}
}
问题:如何模拟没有无参数构造函数的 DI 对象 DtInsightApi 。
解决方案
在检查了有关单元测试的 Spring 文档后,我找到了一个使用@MockBean
.
启动文档: https ://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/html/boot-features-testing.html
根据 Spring 文档,您可以使用@MockBean
来模拟您的 beanApplicationContext
,因此我可以使用@MockBean
来模拟DtInsightApi
.
在运行测试时,有时需要在应用程序上下文中模拟某些组件。例如,您可能有一个在开发期间不可用的远程服务的外观。当您想要模拟在真实环境中可能难以触发的故障时,模拟也很有用。
Spring Boot 包含一个
@MockBean
注解,可用于为您的ApplicationContext
. 您可以使用注解来添加新的 bean,或者替换单个现有的 bean 定义。注释可以直接用于测试类、测试中的字段或@Configuration
类和字段。在字段上使用时,创建的模拟实例也将被注入。在每个测试方法之后,模拟 bean 会自动重置。
我的解决方案:使用@MockBean
and BDDMockito.given(...).willReturn(...)
,用于
@Qualifier("api")
指定 bean 名称,因为@MockBean
通过类类型注入,如果您有相同的类 bean,则需要指定 bean 名称。
我在测试类中的代码:
public class Test{
@MockBean
@Qualifier("api")
private DtInsightApi api;
@Mock
private MockService mockService;
@Before
public void setUp() throws Exception {
// open mock
MockitoAnnotations.openMocks(this);
BDDMockito.given(this.api.getSlbApiClient(MockService.class)).willReturn(mockService);
}
@Autowired
private PendingTestService service;
@Test
public void testUpdate() {
service.update();
}
}
调试mockService可以看到mockService实例是由生成的Mockito
,mock成功了。
您还可以参考 Spring 文档示例:单元测试中的模拟RemoteService
。
import org.junit.*;
import org.junit.runner.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.test.context.*;
import org.springframework.boot.test.mock.mockito.*;
import org.springframework.test.context.junit4.*;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.BDDMockito.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTests {
@MockBean
private RemoteService remoteService;
@Autowired
private Reverser reverser;
@Test
public void exampleTest() {
// RemoteService has been injected into the reverser bean
given(this.remoteService.someCall()).willReturn("mock");
String reverse = reverser.reverseSomeCall();
assertThat(reverse).isEqualTo("kcom");
}
}
推荐阅读
- javascript - 为每个元素的文本节点创建一个变量
- javascript - MVC 5 - jQuery - 在不下载的情况下显示 FileContentResult
- python - 如何从二进制 python 文件中知道文件路径?
- angularjs - 检测在 div 上按下的键
- c# - REST 调用异常:设置内部异常时出现 401 UNAUTHORIZED 循环
- jquery - Bootstrap 模态背景未隐藏
- python - 如何使用 Numba 在 SciPy 中使用任意数量的变量和参数执行多重集成?
- r - R 新手,需要帮助编码功能和理解错误
- sql-server - Azure SQL 数据库 DTU 最大化 - 由于大型数据库?
- javascript - enablejsapi 作为 iframe 元素的属性