Я использую 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, у которого нет конструктора без аргументов.
Вы можете попробовать издеваться над классом строителя и связанными с ним методами. Таким образом, с этим вы вполне можете смоделировать метод build
конструктора и вернуть чисто смоделированный экземпляр DtInsightApi
. Кстати, вы упомянули, что пытались шпионить за этим, но это не сработало. Какую проблему вы заметили?
Привет, akortex91, я отладил MockService mockService = api.getSlbApiClient(MockService.class);
и обнаружил, что экземпляр mockService не является прокси-объектом mockito.
@naimdjon, после DtInsightApi api = Mockito.mock(DtInsightApi.class);
что я должен сделать, чтобы ввести его в весну?
@KDFinal вам нужно будет создать экземпляр службы с макетом или создать тестовую конфигурацию с аннотацией @Bean, которая дает экземпляр DtInsightApi
.
Я обновил свое решение, и оно хорошо работает с небольшими изменениями. @naimdjon
@akortex91 нашел решение для ее решения.
После проверки документов 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");
}
}
Я считаю, что путь
Mockito.mock(DtInsightApi.class)
. И затем вы заглушаете все вызываемые методы.