如何为 Rest 模板编写 Mockito Junit 测试用例?

Posted

技术标签:

【中文标题】如何为 Rest 模板编写 Mockito Junit 测试用例?【英文标题】:How to write Mockito Junit test cases for Rest template? 【发布时间】:2019-06-21 00:41:03 【问题描述】:

从服务中我使用 RestTemplate 调用第三方 api。

@RunWith(MockitoJUnitRunner.class)
public class ForceServiceTest 
@InjectMocks
private ForceService forceService;
@Mock
private RestTemplate restTemplate;
@Before
public void setup() 
    forceService = new ForceService(config, restTemplate);

@Test
public void createTest_valid() throws JSONException 
    /*Mockito.when(restTemplate.exchange(url, HttpMethod.POST, entity, CreateRecordResult.class))
    .thenReturn(response);*/
     Mockito.verify(restTemplate, Mockito.times(1))
    .exchange(Mockito.anyString(),
                    Mockito.<HttpMethod> any(),
                    Mockito.<HttpEntity<?>> any(),
                    Mockito.<Class<?>> any());
    forceService.createLead(lead);


我尝试使用 any() 方法并直接指定值。直接在实体中指定值似乎不是正确的测试方法。 下面是我需要为其编写测试用例的服务类。

@Component
public class ForceService 
    private RestTemplate restTemplate;
public ForceService(ForceServiceConfig config,  RestTemplate restTemplate) 
    this.config = config;
    this.restTemplate = restTemplate;

public String createLead(Lead lead) 
    HttpHeaders headers = new HttpHeaders();
    headers.set(AUTHORIZATION, getAccessToken());
    headers.set(ACCEPT, APPLICATION_JSON);
    headers.set(CONTENT_TYPE, APPLICATION_JSON);
    LeadWrap leadWrap = new LeadWrap();
    leadWrap.setFirstName(lead.getFirstName());
    leadWrap.setLastName(lead.getLastName());
    leadWrap.setEmail(lead.getEmail());
    leadWrap.setPhone(lead.getPhone());

    String jsonString;
    try 
        jsonString = new ObjectMapper().writeValueAsString(leadWrap);

     catch (IOException e) 
        throw new RuntimeException(e);
    
    HttpEntity<String> entity = new HttpEntity<>(jsonString, headers);

    ResponseEntity<CreateRecordResult> exchange = restTemplate.exchange(
            config.restUrl + "/v" + config.restVersion + "/sobjects/Lead/", HttpMethod.POST, entity,
            CreateRecordResult.class);
    if (exchange.getStatusCode().equals(HttpStatus.CREATED)) 
        if (exchange.getBody() != null && exchange.getBody().success) 
            LOGGER.info("Lead record created with Id " + exchange.getBody().id);
            return exchange.getBody().id;
        
        throw new RuntimeException("Record is not created");
     else 
        LOGGER.error(RETURN_STATUS + exchange.getStatusCode());
        throw new RuntimeException(RETURN_STATUS + exchange.getStatusCode());
    

上述测试用例将 ResponseEntity 交换返回为 null。是否有任何解决方案可以使测试用例适用于 RestTemplate 交换调用?

【问题讨论】:

【参考方案1】:

你需要告诉 Mockito 在调用 mock 时要返回什么...

when(restTemplate.exchange(anyString(), any(), any(), any())).thenReturn(...

在 thenReturn 中插入要从调用中返回的 responseEntity。

【讨论】:

我创建了响应并通过了,但它不起作用。此外, ResponseEntity response = new ResponseEntity(result, HttpStatus.CREATED); Mockito.when(restTemplate.exchange(anyString(), any(), any(), any())) .thenReturn(response);抛出错误“方法 exchange(String, HttpMethod, HttpEntity>, Class, Object[]) 对于 RestTemplate 类型不明确” 你能用你得到的修改和错误更新你帖子中的代码吗?【参考方案2】:

验证需要在调用生产代码之后进行,在您的情况下是 createLead() 调用。您还将希望在调用时使用匹配器,这可能不应该被注释掉。在像您这样的情况下,您通常不需要时间和验证。它只会使测试更复杂,更难阅读。

如果我可以断言的服务调用没有返回,我会使用验证。在这些情况下,我会将 when 的所有参数(如果需要通过空指针异常或其他错误)包装在 any() 中,例如 any(HttpEntity.class)anyString(),这样参数就不会模棱两可了。然后您可以使用验证来确认实际参数是否正确。这种策略更容易维护。不幸的是,它通常需要一个参数捕获器来验证标头或其他参数是否正确发送。我说这很不幸,因为测试变得又大又乱,

如果我可以断言结果,我通常只使用 when。在这种情况下,我会用eq() 包装参数,例如eq(httpEntity)。在这种情况下,HttpEntity 类需要有一个好的.equals() 方法,否则它只会使用默认值,并且可能不是很有帮助。但是,它通常非常强大。

您不应使用@InjectMocks 并在设置中进行初始化。如果你 @InjectMocks 它会创建实例并注入模拟。你似乎想把一个真正的配置放进去,这样你就可以使用 setup 方法或者你可以模拟配置。我使用了正确的匹配器,但您可能需要对其进行改进,例如将一些 any() 切换为 eq() 以真正测试您想要测试的内容。我还重新排序,因此操作或生产调用调用在验证之前。该测试应该可以帮助您入门。

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;

import static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;

@RunWith(MockitoJUnitRunner.class)
public class ForceServiceTest 

    private ForceService forceService;
    @Mock
    private RestTemplate restTemplate;

    @Before
    public void setup() 
        forceService = new ForceService(new ForceServiceConfig(), restTemplate);
    

    @Test
    public void createTest_valid() throws Exception 
        when(restTemplate.exchange(anyString(), eq(HttpMethod.POST),
                any(HttpEntity.class),
                eq(CreateRecordResult.class)))
                .thenReturn(new ResponseEntity<>(new CreateRecordResult(), HttpStatus.CREATED));

        forceService.createLead();

        verify(restTemplate, times(1))
                .exchange(eq("config.restUrl/vconfig.restVersion/sobjects/Lead/"),
                        any(HttpMethod.class),
                        any(HttpEntity.class),
                        eq(CreateRecordResult.class));
    

【讨论】:

Mockito.when(restTemplate.exchange( Matchers.eq(url), Matchers.eq(HttpMethod.POST), Matchers.>any(), Matchers.>any()) ).thenReturn(响应);我也试过这个,但没有奏效。它再次返回 null。 @Sowmeashree 写了一个使用您的代码的示例。【参考方案3】:

@DCTID 代码拯救了我的一天。与此同时,我遇到了以下问题并修复了它。 为了模拟 ResponseEntity 的主体,我创建了一个对象并为其设置了值。否则,它没有通过这个条件 - if (exchange.getBody() != null && exchange.getBody().success)

CreateRecordResult createRecordResult = new CreateRecordResult();
createRecordResult.success = true;
Mockito.when(restTemplate.exchange(anyString(), eq(HttpMethod.POST), any(HttpEntity.class),
            eq(CreateRecordResult.class)))
                    .thenReturn(new ResponseEntity<>(createRecordResult, HttpStatus.CREATED));

【讨论】:

以上是关于如何为 Rest 模板编写 Mockito Junit 测试用例?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 django-rest-framework api 编写单元测试?

如何为速度模板编写单元测试?

如何为包含 kafka、postgres 和 rest api docker 容器的应用程序编写 e2e 测试自动化

如何为访问控制编写 Django 模板标签?

如何为 Eclipse 编写代码模板?

如何为动态生成的内容编写自定义表单助手模板?