简体   繁体   English

Mockito:如何模拟 spring 注入的 object 没有无参数构造函数的特殊 DI

[英]Mockito: How to mock spring special DI that the injected object doesn't have no-arg constructor

I'm using Mockito 3.4.6 in unit test, actually, i have integrated Mockito to my unit test and it works well.我在单元测试中使用Mockito 3.4.6 ,实际上,我已经将 Mockito 集成到我的单元测试中并且运行良好。 While, now i need to optimize some unit test, it's a special dependency injection that the injected object doesn't have no-arg constructor , I tried @Spy but it didn't work.虽然,现在我需要优化一些单元测试,这是一个特殊的依赖注入,注入的 object没有无参数构造函数,我试过@Spy但它没有用。

My Test: I tried 1. @Spy ;我的测试:我试过 1. @Spy 2. @Spy with setting instance using = getDtInsightApi() ; 2. @Spy设置实例 using = getDtInsightApi() ; 3. @Spy with @InjectMocks , all of tests are failed. 3. @Spy@InjectMocks ,所有测试都失败了。 As Mockito docs said, seems it can't work for this case.正如 Mockito 文档所说,它似乎不适用于这种情况。

@InjectMocks Mockito will try to inject mocks only either by constructor injection, setter injection, or property injection in order and as described below. @InjectMocks Mockito 将尝试仅通过构造函数注入、setter 注入或属性注入来注入模拟,如下所述。

Also if only use @Spy , it will throw MockitoException :此外,如果仅使用@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.

See my pseudocode as below :请参阅我的伪代码如下

configure class:配置 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 with no public no-arg constructor and get instance by its inner class DtInsightApi class没有公共无参数构造函数并通过其内部 class 获取实例

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...

}

unit test class:单元测试 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 : 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();
   }
}

Question: How to mock the DI object DtInsightApi which doesn't have no-arg constructor .问题:如何模拟没有无参数构造函数的 DI object DtInsightApi。

After checked Spring docs about unit test, I found a solution using @MockBean .在检查了 Spring 有关单元测试的文档后,我找到了使用@MockBean的解决方案。

Spirng docs: https://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/html/boot-features-testing.html Spirng 文档: https://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/html/boot-features-testing.html

According to Spring docs, you can use @MockBean to mock a bean inside your ApplicationContext , so i can use @MockBean to mock DtInsightApi .根据 Spring 文档,您可以使用@MockBeanApplicationContext中模拟一个 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.当您想要模拟在真实环境中可能难以触发的故障时,Mocking 也很有用。

Spring Boot includes a @MockBean annotation that can be used to define a Mockito mock for a bean inside your ApplicationContext . Spring Boot 包含一个@MockBean注释,可用于为ApplicationContext中的 bean 定义 Mockito 模拟。 You can use the annotation to add new beans, or replace a single existing bean definition.您可以使用注解添加新的 bean,或替换单个现有的 bean 定义。 The annotation can be used directly on test classes, on fields within your test, or on @Configuration classes and fields.注释可以直接用于测试类、测试中的字段或@Configuration类和字段。 When used on a field, the instance of the created mock will also be injected.当在一个字段上使用时,创建的 mock 的实例也将被注入。 Mock beans are automatically reset after each test method.模拟 bean 在每个测试方法后自动重置。

My Solution: Use @MockBean and BDDMockito.given(...).willReturn(...) , use @Qualifier("api") to specify the bean name because @MockBean injected by class type, if you have same class beans, you need to specify bean name.我的解决方案:使用@MockBeanBDDMockito.given(...).willReturn(...) ,使用@Qualifier("api")指定 bean 名称,因为@MockBean由 class 类型注入,如果你有相同的 class beans ,您需要指定 bean 名称。

My code in test class:我在测试 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();
    }
}

Debug the mockService you can see mockService instance is generated by Mockito , mock succeed.调试 mockService 可以看到 mockService 实例是由Mockito生成的,mock 成功。

You can also refer to Spring docs example: mock RemoteService in unit test.也可以参考Spring docs example: 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");
    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM