Mockito:如何模拟 spring 注入对象没有无参数构造函数的特殊 DI
Mockito: How to mock spring special DI that the injected object doesn't have no-arg constructor
我在单元测试中使用 Mockito 3.4.6
,实际上,我已经将 Mockito 集成到我的单元测试中并且运行良好。虽然,现在我需要优化一些单元测试,它是一个 特殊依赖注入 ,注入的对象 没有无参数构造函数 ,我试过 @Spy
但没用。
我的测试: 我试了 1. @Spy
; 2. @Spy
with setting instance using = getDtInsightApi()
; 3. @Spy
与 @InjectMocks
,所有测试均失败。正如 Mockito 文档所说,它似乎不适用于这种情况。
@InjectMocks Mockito will try to inject mocks only either by constructor injection,
setter injection, or property injection in order and as described below.
另外如果只使用@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.
查看我的伪代码如下:
配置class:
@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
class 没有 public 无参数构造函数并通过其内部 class[=28 获取实例=]
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...
}
单元测试class:
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
在 ApplicationContext
中模拟一个 bean,所以我可以使用 @MockBean
模拟 DtInsightApi
.
It’s sometimes necessary to mock certain components within your application context when running tests. For example,
you may have a facade over some remote service that’s unavailable during development. Mocking can also be useful when
you want to simulate failures that might be hard to trigger in a real environment.
Spring Boot includes a @MockBean
annotation that can be used to define a Mockito mock for a bean inside your ApplicationContext
.
You can use the annotation to add new beans, or replace a single existing bean definition. The annotation can be used directly on test classes,
on fields within your test, or on @Configuration
classes and fields. When used on a field, the instance of the created mock will also be injected.
Mock beans are automatically reset after each test method.
我的解决方案:使用@MockBean
和BDDMockito.given(...).willReturn(...)
,使用
@Qualifier("api")
指定bean 名称因为@MockBean
由class 类型注入,如果你有相同的class 个bean,你需要指定bean 名称。
我的测试代码class:
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文档示例: mock RemoteService
in unit test.
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");
}
}
我在单元测试中使用 Mockito 3.4.6
,实际上,我已经将 Mockito 集成到我的单元测试中并且运行良好。虽然,现在我需要优化一些单元测试,它是一个 特殊依赖注入 ,注入的对象 没有无参数构造函数 ,我试过 @Spy
但没用。
我的测试: 我试了 1. @Spy
; 2. @Spy
with setting instance using = getDtInsightApi()
; 3. @Spy
与 @InjectMocks
,所有测试均失败。正如 Mockito 文档所说,它似乎不适用于这种情况。
@InjectMocks Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below.
另外如果只使用@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.
查看我的伪代码如下:
配置class:
@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
class 没有 public 无参数构造函数并通过其内部 class[=28 获取实例=]
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...
}
单元测试class:
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
在 ApplicationContext
中模拟一个 bean,所以我可以使用 @MockBean
模拟 DtInsightApi
.
It’s sometimes necessary to mock certain components within your application context when running tests. For example, you may have a facade over some remote service that’s unavailable during development. Mocking can also be useful when you want to simulate failures that might be hard to trigger in a real environment.
Spring Boot includes a
@MockBean
annotation that can be used to define a Mockito mock for a bean inside yourApplicationContext
. You can use the annotation to add new beans, or replace a single existing bean definition. The annotation can be used directly on test classes, on fields within your test, or on@Configuration
classes and fields. When used on a field, the instance of the created mock will also be injected. Mock beans are automatically reset after each test method.
我的解决方案:使用@MockBean
和BDDMockito.given(...).willReturn(...)
,使用
@Qualifier("api")
指定bean 名称因为@MockBean
由class 类型注入,如果你有相同的class 个bean,你需要指定bean 名称。
我的测试代码class:
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文档示例: mock RemoteService
in unit test.
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");
}
}