【问题标题】:JUnit: Unable to mock the RestTemplate object to call postForObject methodJUnit:无法模拟 RestTemplate 对象以调用 postForObject 方法
【发布时间】:2014-07-08 09:51:12
【问题描述】:

我是 Mockito 和 Spring 的 RestTemplate 的新手。我正在为一个向 Web 服务发送请求并通过使用 RestTemplate 获取响应的功能进行 JUnit 测试。我希望服务器响应我想要的响应,以便我可以根据此响应测试功能。我正在使用 Mockito 进行模拟。

我不确定我哪里出错了。我没有创建适当的模拟吗?我的 JSON 对象映射器是否配置不正确?

定义 RestTemplate bean 的配置文件:

<?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 并且没有发现创建 BeanSerializer 的属性(以避免 异常,禁用 SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (通过参考链: 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"]); 嵌套异常是 org.codehaus.jackson.map.JsonMappingException: No 为类找到序列化程序 org.mockito.internal.stubbing.defaultanswers.GloballyConfiguredAnswer 并且没有发现创建 BeanSerializer 的属性(以避免 异常,禁用 SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (通过参考链: 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"])

还有:

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"])

【问题讨论】:

    标签: junit mockito resttemplate springmockito


    【解决方案1】:

    如上所述,要使用 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);
        }    
    }
    

    【讨论】:

      【解决方案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));
          }
       }
      

      我还发现测试其他框架类是不好的做法,更多时候他们没有测试过他们的类,而你只是复制他们的工作。

      【讨论】:

      • 感谢您的回答。我能够解决我的问题。我的方法有点错误。我将很快发布解决方案。
      • @AshAsh 你能尽快发帖吗
      猜你喜欢
      • 2018-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-22
      • 2019-07-01
      • 2020-07-28
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多