JUnit: невозможно смоделировать объект RestTemplate для вызова метода postForObject
Я новичок в Mockito, а также в Spring RestTemplate. Я работаю над тестами JUnit для функциональности, которая отправляет запрос в веб-сервис и получает ответ с помощью RestTemplate. Я хочу, чтобы сервер ответил желаемым ответом, чтобы я мог проверить функциональность на основе этого ответа. Я использую Mockito для насмешек.
Я не уверен, где я иду не так. Разве я не создаю правильные издевательства? Идентификатор объекта JSON не был настроен правильно?
Файл конфигурации, определяющий компонент RestTemplate:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />
<bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"/>
<bean class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
<property name="marshaller" ref="xsStreamMarshaller" />
<property name="unmarshaller" ref="xsStreamMarshaller" />
</bean>
</list>
</property>
</bean>
<bean id="xsStreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"></bean>
</beans>
Мой DTO:
import org.codehaus.jackson.annotate.JsonWriteNullProperties;
@JsonWriteNullProperties(false)
public abstract class BaseDTO {
protected boolean error;
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
}
public class ChildDTO extends CommercialBaseDTO {
private String fullName;
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}
Класс, содержащий метод для тестирования:
package com.exmpale.mypackage;
import org.springframework.web.client.RestTemplate;
@Component
public class MyUtilClass {
@Autowired
private RestTemplate restTemplate;
public RestTemplate getRestTemplate(){
return restTemplate;
}
public void setRestTemplate(RestTemplate restTemplate){
this.restTemplate = restTemplate;
}
// Method to test
public ChildDTO getChildDTO(MyUser myUser, HttpServletRequest request, HttpServletResponse response)
{
response.setContentType("application/json");
//Nothing much here, it takes the myUser and convert into childDTO
ChildDTO childDTO = new MyUtilClass().getDTOFromUser(request, myUser);
//This is the restTemplate that iam trying to mock.
childDTO = restTemplate.postForObject("http://www.google.com", childDTO, ChildDTO.class);
if (childDTO.isError()) {
//Then do some stuff.........
}
return childDTO;
}
}
Тестовый класс JUnit
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"test-config.xml"})
public class MyUtilClassTest {
@InjectMocks
RestTemplate restTemplate= new RestTemplate();
private MockRestServiceServer mockServer;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
//Creating the mock server
//Add message conveters
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
messageConverters.add(new FormHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new MappingJacksonHttpMessageConverter());
//Create Object mapper
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure( DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.configure( SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_FIELDS, true);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_GETTERS,true);
objectMapper.configure( SerializationConfig.Feature.AUTO_DETECT_IS_GETTERS,true);
MappingJacksonHttpMessageConverter jsonMessageConverter = new MappingJacksonHttpMessageConverter();
jsonMessageConverter.setObjectMapper(objectMapper);
messageConverters.add(jsonMessageConverter);
//Set the message converters
restTemplate.setMessageConverters(messageConverters);
mockServer = MockRestServiceServer.createServer(restTemplate);
}
@Test
public void testGetChildDTO()throws Exception {
MyUtilClass myUtil = new MyUtilClass();
MyUser myUser = new MyUser();
HttpServletRequest request = new HttpServletRequestWrapper(new MockHttpServletRequest());
HttpServletResponse response = new HttpServletResponseWrapper(new MockHttpServletResponse());
//create the mocks for ChildDTO. I want MyUtilClass().getDTOFromUser(request, myUser) to return this.
ChildDTO childDTOMock_One = Mockito.mock(ChildDTO);
//Want this to be returned when restTemplate.postForObject() is called.
ChildDTO childDTOMock_Two = Mockito.mock(ChildDTO.class);
childDTOMock_Two.setError(false);
//create the mocks for userMgntUtils
MyUtilClass myUtilClassMock = Mockito.mock(MyUtilClass.class);
//stub the method getDTOFromUser() to return the mock object. I need this mock to be passed to 'postForObject()'
Mockito.when(myUtilClassMock.getDTOFromUser(request, myUser)).thenReturn(childDTOMock_One);
String responseJSON="{\"error\":false}";
//set the expectation values for mockServer
mockServer.expect( requestTo("http://www.google.com")).andExpect(method(HttpMethod.POST)).andRespond(withSuccess(responseJSON,MediaType.APPLICATION_JSON));
//set the expectation values for restTemplate
Mockito.when(restTemplate.postForObject( "http://www.google.com", childDTOMock_One, ChildDTO.class)).thenReturn(childDTOMock_Two);
TypedUserDTO result = userMgmtUtils.getUserProfileDTO(registerUser, request, response, action);
assertNotNull(result);
}
}
Получаем следующее исключение:
org.springframework.http.converter.HttpMessageNotWritableException: не удалось записать JSON: не найден сериализатор для класса org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer и не обнаружены свойства для создания BeanSerializerFonizer (чтобы исключить исключение.) (через цепочку ссылок: com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"]); вложенным исключением является org.codehaus.jackson.map.JsonMappingException: не найден сериализатор для класса org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer, и не обнаружено никаких свойств для создания BeanSerializer (чтобы избежать исключения, отключите SerializationConfig.Feature.P)_EJ через цепочку ссылок: com.biogenidec.dto.TypedUserDTO $$ EnhancerByMockitoWithCGLIB $$ bee3c447 ["обратные вызовы"] -> org.mockito.internal.creation.MethodInterceptorFilter ["обработчик"] -> org.mockitoo.InterierHier "mockSettings"] -> org.mockito.internal.creation.settings.CreationSettings [ "defaultAnswer"])
А также:
Caused by: org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.biogenidec.dto.TypedUserDTO$$EnhancerByMockitoWithCGLIB$$bee3c447["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["mockSettings"]->org.mockito.internal.creation.settings.CreationSettings["defaultAnswer"])
2 ответа
Идея Mockito заключается в тестировании класса и отсутствия зависимостей вне его. Так что если ваше тестирование MyUtilClass
Вы хотите издеваться над RestTemplate
учебный класс. И ваши @InjectMocks находятся не в том классе, см. Ниже.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"test-config.xml"})
public class MyUtilClassTest
{
@Mock
private RestTemplate restTemplate;
@InjectMocks
private MyUtilClass myUtilClass;
@Before
public void setUp() throws Exception
{
MockitoAnnotations.initMocks(this);
}
@Test
public void testGetChildDTO()throws Exception
{
MyUser myUser = new MyUser();
HttpServletRequest request = new HttpServletRequestWrapper(new MockHttpServletRequest());
HttpServletResponse response = new HttpServletResponseWrapper(new MockHttpServletResponse());
Mockito.when(RestTemplate.postForObject(Mockito.eq("http://www.google.com",
Mockito.any(ChildDTO.class), Mockito.eq(ChildDTO.class)))).thenAnswer(
new Answer<ChildDTO>()
{
@Override
public ChildDTO answer(InvocationOnMock invocation) throws Throwable
{
//The below statement takes the second argument passed into the method and returns it
return (ChildDTO) invocation.getArguments()[1];
}
});
ChildDTO childDTO = myUtilClass.getDTOFromUser(request, myUser);
//then verify that the restTemplate.postForObject mock was called with the correct parameters
Mockito.verify(restTemplate, Mockito.times(1)).postForObject(Mockito.eq("http://www.google.com",
Mockito.eq(childDTO), Mockito.eq(ChildDTO.class));
}
}
Также я считаю плохой практикой тестировать другие классы фреймворков, чаще всего они уже не тестировали свой класс, а вы просто дублировали их работу.
Как правильно отмечено выше, чтобы протестировать ваш метод с помощью mockito, нет необходимости инициализировать restTemplate. Достаточно проверить, что параметры ввода верны (если необходимо) и вернуть правильный макет объекта из restTemplate.
Мы не тестируем restTemplate здесь, мы только тестируем наш код. Это цель юнит-тестов.
Вы можете сделать что-то вроде этого или что-то более простое:
@RunWith(value = MockitoJUnitRunner.class)
public class Test {
@InjectMocks
private MyUtilClass testObj;
@Mock
private RestTemplate restTemplate;
@Mock
MyUser myUser;
@Mock
HttpServletRequest request;
@Mock
HttpServletResponse response;
@Test
public void test() throws Exception {
//Configure sample to comparison and verification the result of the method:
ChildDTO sample = getSample();
//configure mocks:
ChildDTO myObject = new ChildDTO();
//configure myObject properties
ResponseEntity<ChildDTO> respEntity = new ResponseEntity<>(
myObject, HttpStatus.ACCEPTED);
when(restTemplate.postForObject(anyString(), Matchers.<HttpEntity<?>>any(),
Matchers.any(Class.class))).thenReturn(respEntity);
//other stuff to configure correct behaviour of mocks request, response e.t.c.
//act:
ChildDTO result = testObj.getChildDTO(myUser, request, response);
//verify that correct parameters were passed into restTemplate method "postForObject":
verify(restTemplate).postForObject(eq("http://www.google.com"), Matchers.<HttpEntity<?>>any(),
eq(ChildDTO.class)).thenReturn(respEntity);
//assert to verify that we got correct result:
assertEquals(sample, result);
}
}