【问题标题】:How do I mock a REST template exchange?如何模拟 REST 模板交换?
【发布时间】:2017-01-22 00:41:05
【问题描述】:

我有一项服务,我需要通过 rest 向外部服务器询问一些信息:

public class SomeService {

    public List<ObjectA> getListofObjectsA() {
        List<ObjectA> objectAList = new ArrayList<ObjectA>();
        ParameterizedTypeReference<List<ObjectA>> typeRef = new ParameterizedTypeReference<List<ObjectA>>() {};
        ResponseEntity<List<ObjectA>> responseEntity = restTemplate.exchange("/objects/get-objectA", HttpMethod.POST, new HttpEntity<>(ObjectAList), typeRef);
        return responseEntity.getBody();
    }
}

如何为getListofObjectsA() 编写 JUnit 测试?

我尝试过以下方法:

@RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
    private MockRestServiceServer mockServer;

    @Mock
    private RestTemplate restTemplate;

    @Inject
   private SomeService underTest;

@Before
public void setup() {
    mockServer = MockRestServiceServer.createServer(restTemplate);
    underTest = new SomeService(restTemplate);
    mockServer.expect(requestTo("/objects/get-objectA")).andExpect(method(HttpMethod.POST))
            .andRespond(withSuccess("{json list response}", MediaType.APPLICATION_JSON));
}

    @Test
    public void testGetObjectAList() {
    List<ObjectA> res = underTest.getListofObjectsA();
    Assert.assertEquals(myobjectA, res.get(0));
}

但是上面的代码不起作用,它表明responseEntittynull。如何更正我的测试以正确模拟 restTemplate.exchange

【问题讨论】:

  • 有人有想法吗?

标签: java spring-boot junit mockito


【解决方案1】:

你不需要MockRestServiceServer 对象。注释是@InjectMocks 而不是@Inject。下面是一个应该可以工作的示例代码

@RunWith(MockitoJUnitRunner.class)
public class SomeServiceTest {
    @Mock
    private RestTemplate restTemplate;

    @InjectMocks
    private SomeService underTest;

    @Test
    public void testGetObjectAList() {
        ObjectA myobjectA = new ObjectA();
        //define the entity you want the exchange to return
        ResponseEntity<List<ObjectA>> myEntity = new ResponseEntity<List<ObjectA>>(HttpStatus.ACCEPTED);
        Mockito.when(restTemplate.exchange(
            Matchers.eq("/objects/get-objectA"),
            Matchers.eq(HttpMethod.POST),
            Matchers.<HttpEntity<List<ObjectA>>>any(),
            Matchers.<ParameterizedTypeReference<List<ObjectA>>>any())
        ).thenReturn(myEntity);

        List<ObjectA> res = underTest.getListofObjectsA();
        Assert.assertEquals(myobjectA, res.get(0));
    }

【讨论】:

  • 我们如何向 myEntity.getBody() 添加内容?我的返回 null
  • 不推荐使用匹配器,您应该使用 Mockito
  • @Angelina 因为它是一个真实的对象,只需使用适当的构造函数,即ResponseEntity(T body, HttpStatus status)
  • @Marino 确保调用了应该返回响应实体的模拟方法,并且它实际上已配置为返回所述实体
  • 现在不推荐使用 Matchers 类
【解决方案2】:

这是一个不推荐使用 ArgumentMatchers 类的示例

when(restTemplate.exchange(
                ArgumentMatchers.anyString(),
                ArgumentMatchers.any(HttpMethod.class),
                ArgumentMatchers.any(),
                ArgumentMatchers.<Class<String>>any()))
             .thenReturn(responseEntity);

【讨论】:

  • 您也可以将它与通用匹配器一起使用。 ArgumentMatchers.>any())
【解决方案3】:
ResponseEntity<String> responseEntity = new ResponseEntity<String>("sampleBodyString", HttpStatus.ACCEPTED);
when(restTemplate.exchange(
                           Matchers.anyString(), 
                           Matchers.any(HttpMethod.class),
                           Matchers.<HttpEntity<?>> any(), 
                           Matchers.<Class<String>> any()
                          )
                         ).thenReturn(responseEntity);

【讨论】:

  • 不适用于 org.hamcrest.Matchersorg.mockito.Matchers 已被弃用,取而代之的是 hamcrest。
  • “Matchers”已被弃用,由于名称冲突,应替换为“ArgumentMatchers”。 Class Matchers
【解决方案4】:

对我来说,我必须使用 Matchers.any(URI.class)

Mockito.when(restTemplate.exchange(Matchers.any(URI.class), Matchers.any(HttpMethod.class), Matchers.<HttpEntity<?>> any(), Matchers.<Class<Object>> any())).thenReturn(myEntity);

【讨论】:

  • 就我而言:when(restTemplate.exchange(any(String.class), eq(HttpMethod.GET), any(), eq(new ParameterizedTypeReference&lt;String&gt;() {}))).thenReturn(myEntity);
【解决方案5】:

这项工作在我这边。

ResourceBean resourceBean = initResourceBean();
ResponseEntity<ResourceBean> responseEntity  
    = new ResponseEntity<ResourceBean>(resourceBean, HttpStatus.ACCEPTED);
when(restTemplate.exchange(
    Matchers.anyObject(), 
    Matchers.any(HttpMethod.class),
    Matchers.<HttpEntity> any(), 
    Matchers.<Class<ResourceBean>> any())
 ).thenReturn(responseEntity);

【讨论】:

    【解决方案6】:

    我曾经遇到过这样的错误。我找到了一个更可靠的解决方案。我也提到了对我有用的导入语句。下面这段代码完美地模拟了 restemplate。

    导入 org.mockito.Matchers;
    导入静态 org.mockito.Matchers.any;

        HttpHeaders headers = new Headers();
        headers.setExpires(10000L);     
        ResponseEntity<String> responseEntity = new ResponseEntity<>("dummyString", headers, HttpStatus.OK);
        when(restTemplate.exchange( Matchers.anyString(), 
                Matchers.any(HttpMethod.class),
                Matchers.<HttpEntity<?>> any(), 
                Matchers.<Class<String>> any())).thenReturn(responseEntity);
    

    【讨论】:

      【解决方案7】:

      RestTemplate 实例必须是真实对象。如果您创建RestTemplate 的真实实例并将其设为@Spy,它应该可以工作。

      @Spy
      private RestTemplate restTemplate = new RestTemplate();
      

      【讨论】:

        【解决方案8】:

        如果您使用的是RestTemplateBuilder,那么通常的做法可能不起作用。您需要将其与 when(condition) 一起添加到您的测试类中。

        @Before    
        public void setup() {        
             ReflectionTestUtils.setField(service, "restTemplate", restTemplate);    
        }
        

        【讨论】:

        • 我正在使用常规的休息模板,这是唯一有效的方法
        【解决方案9】:

        假设你有一个如下的交换电话:

        String url = "/zzz/{accountNumber}";
        
        Optional<AccountResponse> accResponse = Optional.ofNullable(accountNumber)
                .map(account -> {
        
        
                    HttpHeaders headers = new HttpHeaders();
                    headers.setContentType(MediaType.APPLICATION_JSON);
                    headers.set("Authorization", "bearer 121212");
        
        
                    HttpEntity<Object> entity = new HttpEntity<>(headers);
                    ResponseEntity<AccountResponse> response = template.exchange(
                            url,
                            GET,
                            entity,
                            AccountResponse.class,
                            accountNumber
                    );
        
                    return response.getBody();
                });
        
        

        要在您的测试用例中模拟它,您可以使用 mocitko,如下所示:

        when(restTemplate.exchange(
                ArgumentMatchers.anyString(),
                ArgumentMatchers.any(HttpMethod.class),
                ArgumentMatchers.any(),
                ArgumentMatchers.<Class<AccountResponse>>any(),
                ArgumentMatchers.<ParameterizedTypeReference<List<Object>>>any())
        )
        
        

        【讨论】:

          【解决方案10】:

          我实现了a small library,这非常有用。它提供了一个ClientHttpRequestFactory 可以接收一些上下文。通过这样做,它允许遍历所有客户端层,例如检查查询参数是否被赋值、标头设置以及检查反序列化是否正常工作。

          【讨论】:

            【解决方案11】:

            如果您的意图是测试服务而不关心其余调用,我建议您不要在单元测试中使用任何注释以简化测试。

            所以,我的建议是重构您的服务以使用注入构造函数接收 resttemplate。这将有助于测试。示例:

            @Service
            class SomeService {
                @AutoWired
                SomeService(TestTemplateObjects restTemplateObjects) {
                    this.restTemplateObjects = restTemplateObjects;
                }
            }
            

            RestTemplate 作为组件,之后被注入和模拟:

            @Component
            public class RestTemplateObjects {
            
                private final RestTemplate restTemplate;
            
                public RestTemplateObjects () {
                    this.restTemplate = new RestTemplate();
                    // you can add extra setup the restTemplate here, like errorHandler or converters
                }
            
                public RestTemplate getRestTemplate() {
                    return restTemplate;
                }
            }
            

            还有测试:

            public void test() {
            
                when(mockedRestTemplateObject.get).thenReturn(mockRestTemplate);
            
                //mock restTemplate.exchange
                when(mockRestTemplate.exchange(...)).thenReturn(mockedResponseEntity);
            
                SomeService someService = new SomeService(mockedRestTemplateObject);
                someService.getListofObjectsA();
            }
            

            通过这种方式,您可以通过 SomeService 构造函数直接访问模拟其余模板。

            【讨论】:

              【解决方案12】:

              如果有人仍然面临这个问题,Captor 注释对我有用

              @Captor
              private ArgumentCaptor<Object> argumentCaptor;
              

              然后我可以通过以下方式模拟请求:

              ResponseEntity<YourTestResponse> testEntity = new ResponseEntity<>(
                  getTestFactoryResponse(), 
                  HttpStatus.OK);
              when(mockRestTemplate.exchange((String) argumentCaptor.capture(), 
                  (HttpMethod) argumentCaptor.capture(), 
                  (HttpEntity<?>) argumentCaptor.capture(), 
                  (Class<YourTestResponse.class>) any())
              ).thenReturn(testEntity);
              

              【讨论】:

                【解决方案13】:

                如果有人在尝试模拟 restTemplate.exchange(...) 时遇到此问题,则问题似乎出在匹配器上。例如:以下行不通,

                when(ecocashRestTemplate.exchange(Mockito.any()
                                , Mockito.eq(HttpMethod.GET)
                                , Mockito.any(HttpEntity.class)
                                , Mockito.<Class<UserTransaction>>any())
                        ).thenReturn(new ResponseEntity<>(transaction, HttpStatus.OK));
                

                但是这个真的会起作用:

                  ResponseEntity<UserTransaction> variable = new ResponseEntity<>(transaction, HttpStatus.OK);
                  when(ecocashRestTemplate.exchange(Mockito.anyString()
                                , Mockito.eq(HttpMethod.GET)
                                , Mockito.any(HttpEntity.class)
                                , Mockito.<Class<UserTransaction>>any())
                   ).thenReturn(new ResponseEntity<>(transaction, HttpStatus.OK));
                

                注意第二个块上的 Mockito.anyString() 与 theMockito.any()。

                【讨论】:

                  【解决方案14】:

                  当我们测试使用 restTemplate 与某个外部系统通信的客户端时,作为单元测试的一部分,我们需要验证我们发送的 httpEntity、标头和参数。

                  ArgumentCaptor 在这些情况下会派上用场。所以这是我的例子(工作代码)

                  @Mock
                  private RestTemplate restTemplate;
                  
                  @InjectMocks
                  private MyClient client;
                  
                  @Captor
                  ArgumentCaptor<HttpEntity<?>> httpEntityCaptor;
                  
                  when(restTemplate.exchange(eq(expectedUrl), eq(HttpMethod.POST), Matchers.any(HttpEntity.class), eq(MyTargetResponse.class)).thenReturn(expectedResponse);
                  
                  
                  
                  verify(restTemplate).exchange(eq(expectedUrl),eq(HttpMethod.POST), httpEntityCaptor.captor(),eq(MyTargetResponse.class));
                  
                  HttpEntity<?> actualResponse = httpEntityCaptor.getValue();
                  
                  HttpHeaders actualResponse.getHeaders();
                  
                  assertEquals(headers.getFirst("Content-Type", "application/json")
                  

                  现在可以根据您的用例进行断言,因为您已经获得了发送的捕获对象。

                  【讨论】:

                    【解决方案15】:

                    对于这个特定的 exchange() 案例, 我发现把它存起来更简单,好旧的覆盖:

                    var restTemplate = new RestTemplate() {
                      public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, @Nullable HttpEntity<?> requestEntity,
                                                                  Class<T> responseType) throws RestClientException {
                                throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
                            }
                        };
                    

                    更少的模拟东西.. 尤其是那个 api 总是在变化。 eq(..) any().. 等等

                    您可以在返回某些内容或抛出异常之前检查存根 exchange() 中的 arg。

                    -- 我知道这不是那个严格问题的答案。但同样的结果。代码更少,更易于支持。

                    【讨论】:

                      【解决方案16】:

                      使用 mockito-core-2.23.4

                      ResponseEntity<YOUR_CLASS> responseEntity = new ResponseEntity(YOUR_CLASS_OBJECT, HttpStatus.OK);
                      
                              when(restTemplate.exchange(Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.<ParameterizedTypeReference<YOUR_CLASS>> any()))
                                      .thenReturn(responseEntity);
                      

                      【讨论】:

                        猜你喜欢
                        • 2020-03-08
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2018-10-20
                        • 2021-06-20
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-11-28
                        相关资源
                        最近更新 更多