簡體   English   中英

Mockito:如何模擬 spring 注入的 object 沒有無參數構造函數的特殊 DI

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

我在單元測試中使用Mockito 3.4.6 ,實際上,我已經將 Mockito 集成到我的單元測試中並且運行良好。 雖然,現在我需要優化一些單元測試,這是一個特殊的依賴注入,注入的 object沒有無參數構造函數,我試過@Spy但它沒有用。

我的測試:我試過 1. @Spy 2. @Spy設置實例 using = 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.

請參閱我的偽代碼如下

配置 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沒有公共無參數構造函數並通過其內部 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...

}

單元測試 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 object DtInsightApi。

在檢查了 Spring 有關單元測試的文檔后,我找到了使用@MockBean的解決方案。

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

根據 Spring 文檔,您可以使用@MockBeanApplicationContext中模擬一個 bean ,因此我可以使用@MockBean來模擬DtInsightApi

在運行測試時,有時需要在應用程序上下文中模擬某些組件。 例如,您可能擁有一些在開發期間不可用的遠程服務的外觀。 當您想要模擬在真實環境中可能難以觸發的故障時,Mocking 也很有用。

Spring Boot 包含一個@MockBean注釋,可用於為ApplicationContext中的 bean 定義 Mockito 模擬。 您可以使用注解添加新的 bean,或替換單個現有的 bean 定義。 注釋可以直接用於測試類、測試中的字段或@Configuration類和字段。 當在一個字段上使用時,創建的 mock 的實例也將被注入。 模擬 bean 在每個測試方法后自動重置。

我的解決方案:使用@MockBeanBDDMockito.given(...).willReturn(...) ,使用@Qualifier("api")指定 bean 名稱,因為@MockBean由 class 類型注入,如果你有相同的 class beans ,您需要指定 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 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