Mockito: как смоделировать специальный DI Spring, чтобы у введенного объекта не было конструктора без аргументов

Я использую Mockito 3.4.6 в модульном тесте, на самом деле я интегрировал Mockito в свой модульный тест, и он работает хорошо. Хотя теперь мне нужно оптимизировать какой-то модульный тест, это специальная инъекция зависимостей, что внедренный объект не имеет конструктора без аргументов, я пробовал @Spy, но это не сработало.

Мой тест: я пробовал 1. @Spy; 2. @Spy с установкой экземпляра с помощью = getDtInsightApi(); 3. @Spy с @InjectMocks все тесты провалены. Как сказано в документах Mockito, похоже, в этом случае это не сработает.

@InjectMocks Mockito попытается внедрить макеты только путем внедрения конструктора, инъекция сеттера или инъекция свойства в порядке и так, как описано ниже.

Также, если использовать только @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, у которого нет конструктора без аргументов.

Я считаю, что путь Mockito.mock(DtInsightApi.class). И затем вы заглушаете все вызываемые методы.

naimdjon 22.12.2020 12:17

Вы можете попробовать издеваться над классом строителя и связанными с ним методами. Таким образом, с этим вы вполне можете смоделировать метод build конструктора и вернуть чисто смоделированный экземпляр DtInsightApi. Кстати, вы упомянули, что пытались шпионить за этим, но это не сработало. Какую проблему вы заметили?

akortex 22.12.2020 13:00

Привет, akortex91, я отладил MockService mockService = api.getSlbApiClient(MockService.class); и обнаружил, что экземпляр mockService не является прокси-объектом mockito.

Rollsbean 22.12.2020 15:00

@naimdjon, после DtInsightApi api = Mockito.mock(DtInsightApi.class); что я должен сделать, чтобы ввести его в весну?

Rollsbean 22.12.2020 15:09

@KDFinal вам нужно будет создать экземпляр службы с макетом или создать тестовую конфигурацию с аннотацией @Bean, которая дает экземпляр DtInsightApi.

naimdjon 22.12.2020 19:39

Я обновил свое решение, и оно хорошо работает с небольшими изменениями. @naimdjon

Rollsbean 23.12.2020 05:03

@akortex91 нашел решение для ее решения.

Rollsbean 23.12.2020 05:04
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
7
1 420
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

После проверки документов Spring о модульном тесте я нашел решение, используя @MockBean.

Документы Spirng:https://docs.spring.io/spring-boot/docs/1.5.2.RELEASE/reference/html/boot-features-testing.html

Согласно документам Spring, вы можете использовать @MockBean, чтобы издеваться над bean-компонентом внутри вашего ApplicationContext, поэтому я могу использовать @MockBean, чтобы издеваться над DtInsightApi.

Иногда при выполнении тестов необходимо имитировать определенные компоненты в контексте вашего приложения. Например, у вас может быть фасад над какой-то удаленной службой, которая недоступна во время разработки. Насмешки также могут быть полезны, когда вы хотите имитировать сбои, которые сложно вызвать в реальной среде.

Spring Boot включает аннотацию @MockBean, которую можно использовать для определения макета Mockito для bean-компонента внутри вашего ApplicationContext. Вы можете использовать аннотацию для добавления новых bean-компонентов или замены одного существующего определения bean-компонента. Аннотацию можно использовать непосредственно в тестовых классах, на полях вашего теста или на @Configuration классах и полях. При использовании в поле также будет внедрен экземпляр созданного макета. Мок-бины автоматически сбрасываются после каждого метода тестирования.

Мое решение: используйте @MockBean и BDDMockito.given(...).willReturn(...), используйте @Qualifier("api") чтобы указать имя компонента, потому что @MockBean вводится типом класса, если у вас есть компоненты одного класса, вам необходимо указать имя компонента.

Мой код в тестовом классе:

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, имитировать успешно.

Вы также можете обратиться к примеру документации Spring: mock 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");
    }

}

Другие вопросы по теме