使用 Mockito 在客户端测试 POST 请求

Posted

技术标签:

【中文标题】使用 Mockito 在客户端测试 POST 请求【英文标题】:Testing POST request in client side using Mockito 【发布时间】:2019-08-07 14:56:54 【问题描述】:

我想测试应该向“服务器”发送发布请求的发布方法(所以我想模拟来自服务器的响应并检查响应)。另外,我想测试响应在正文中是否包含 http 状态 OK。问题:我应该如何使用 mockito 来做到这一点?

我在客户端(客户端)中的发布方法:

public class Client
        public static void sendUser()

        String url = "http://localhost:8080/user/add";

        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
        requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        User test = new User();
        test.setName("test");
        test.setEmail("a@hotmail.com");
        test.setScore(205);

        RestTemplate restTemplate = new RestTemplate();
        HttpEntity<User> request = new HttpEntity<>(test);

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);

        if(response.getStatusCode() == HttpStatus.OK)
            System.out.println("user response: OK");
        

      
  

另一个模块中的我的控制器(服务器端):

@RestController
@RequestMapping("/user")
public class UserController

    @Autowired
    private UserRepository userRepository;

    @PostMapping("/add")
    public ResponseEntity addUserToDb(@RequestBody User user) throws Exception
    
        userRepository.save(user);
        return ResponseEntity.ok(HttpStatus.OK);
    

测试:

    @RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest(classes = Client.class)
@AutoConfigureMockMvc
public class ClientTest


    private MockRestServiceServer mockServer;

    @Autowired
    private RestTemplate restTemplate; 

    @Autowired
    private MockMvc mockMvc;

    @Before
    public void configureRestMVC()
    
        mockServer =
                MockRestServiceServer.createServer(restTemplate);
    

    @Test
    public void testRquestUserAddObject() throws Exception
    

        User user = new User("test", "mail", 2255);

        Gson gson = new Gson();

        String json = gson.toJson(user );

        mockServer.expect(once(), requestTo("http://localhost:8080/user/add")).andRespond(withSuccess());


        this.mockMvc.perform(post("http://localhost:8080/user/add")
                .content(json)
                .contentType(MediaType.APPLICATION_JSON))
                .andDo(print()).andExpect(status().isOk())
                .andExpect(content().json(json));
    


现在我收到了这个错误:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'ClientTest': Unsatisfied dependency expressed through field 'restTemplate'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.web.client.RestTemplate' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: @org.springframework.beans.factory.annotation.Autowired(required=true)

【问题讨论】:

测试应该向服务器发送 post 请求的 post 方法,所以你想要一个真正的服务器调用而不是 mock 不,我想模拟响应 那么看起来不像 向服务器发送一个 post 请求,而是你想模拟一些 POST 请求的响应?对吗? 是的,我想模拟这个特定 POST 请求的 POST 响应 ***.com/questions/39486521/…、***.com/questions/50271164/… 和 javased.com/?api=org.springframework.web.client.RestTemplate 这些是一些有用的链接..建议尝试一下,如果卡住了,请更新您的问题。 【参考方案1】:

根据您的客户端类,建议进行以下更改以使其更好地可测试:

    //  class
    public class Client 

        /*** restTemplate unique instance for every unique HTTP server. ***/
        @Autowired
        RestTemplate restTemplate;

        public ResponseEntity<String> sendUser() 

        String url = "http://localhost:8080/user/add";

        HttpHeaders requestHeaders = new HttpHeaders();
        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
        requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));

        User test = new User();
        test.setName("test");
        test.setEmail("a@hotmail.com");
        test.setScore(205);

        HttpEntity<User> request = new HttpEntity<>(test);

        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request, String.class);

        if(response.getStatusCode() == HttpStatus.OK)
            System.out.println("user response: OK");
        
        return response;
      
  

然后对于上面我们Junit作为:

@RunWith(MockitoJUnitRunner.class)
public class ClientTest 

  private String RESULT = "Assert result";

  @Mock
  private RestTemplate restTemplate;

  @InjectMocks
  private Client client;

  /**
   * any setting needed before load of test class
   */
  @Before
  public void setUp() 
    // not needed as of now
  

  // testing an exception scenario
  @Test(expected = RestClientException.class)
  public void testSendUserForExceptionScenario() throws RestClientException 

    doThrow(RestClientException.class).when(restTemplate)
        .exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), any(Class.class));
    // expect RestClientException
    client.sendUser();
  

  @Test
  public void testSendUserForValidScenario() throws RestClientException 

    // creating expected response
    User user= new User("name", "mail", 6609); 
    Gson gson = new Gson(); 
    String json = gson.toJson(user); 
    doReturn(new ResponseEntity<String>(json, HttpStatus.OK)).when(restTemplate)
        .exchange(anyString(), any(HttpMethod.class), any(HttpEntity.class), any(Class.class));
    // expect proper response
    ResponseEntity<String> response =
        (ResponseEntity<String>) client.sendUser();
    assertEquals(this.RESULT, HttpStatus.OK, response.getStatusCode());
  

基本上在您的sendResponse() 函数中,我们这样做:

// we are getting URL , creating requestHeader
// finally creating HttpEntity<User> request 
// and then passing them restTemplate.exchange 
// and then restTemplate is doing its job to make a HTPP connection and getresponse...
// and then we are prinnting the response... somestuff 

因此,在相应的测试中,我们也应该只测试函数在做什么 由于restTemplate 正在处理连接,并且您没有覆盖restTemplate 的任何工作,所以我们不应该为此做任何事情...... 而只是测试我们的代码/逻辑。

最后,确保导入看起来像:

可以肯定的是,导入将是这样的:

import org.springframework.http.HttpEntity; 
import org.springframework.http.HttpHeaders; 
import org.springframework.http.HttpMethod; 
import org.springframework.http.HttpStatus; 
import org.springframework.http.MediaType; 
import org.springframework.http.ResponseEntity; 
import org.springframework.web.client.RestTemplate;

希望这会有所帮助。

【讨论】:

嘿 VibrantVivek,我现在在执行 addUser() 方法时遇到错误,因为 restTemplate 是自动装配的,我真的不想用 SpringBoot 运行 Client 类。因此,它给出了一个错误,表明 restTemplate 为 NuLL。那么我怎样才能让它在不自动装配的情况下运行并且仍然让测试工作呢? 它不需要是 Springboot 应用程序来使用 Autowired..而只有 Springwebmvc 应该做。让我们继续:here【参考方案2】:

先完整代码(解释如下):

import static org.springframework.test.web.client.ExpectedCount.manyTimes;
import static org.springframework.test.web.client.ExpectedCount.once;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
@AutoConfigureMockMvc
public class MyTestClass 

MockRestServiceServer mockServer;

    @Autowired
    private RestTemplate restTemplate;  //create a bean somewhere. It will be injected here. 

    @Autowired
    private MockMvc mockMvc;

    @Before
    public void configureRestMVC()
        mockServer =
                MockRestServiceServer.createServer(restTemplate);
    

    @Test
    public void test0() throws Exception 
        //this is where you would mock the call to endpoint and and response
        mockServer.expect(once(), requestTo("www.example.com/endpoint1"))
        .andRespond(withSuccess());
    ... 
    //here you will actually make a call to your controller. If the service class is making a post call to another endpoint outside, that you just mocked in above statement.
    this.mockMvc.perform(post("www.example2.com/example2endpoint")
                .content(asJsonString(new YouCustomObjectThatYouWantToPost))
                .contentType(MediaType.APPLICATION_JSON))
        .andDo(print()).andExpect(status().isOk())
        .andExpect(content().json(matchResponseAgainstThisObject()));
   

您需要使用@AutoConfigureMockMvc 注释。背后的目的是根本不启动服务器,而只测试其下的层,Spring 处理传入的 HTTP 请求并将其交给您的控制器。这样,几乎使用了整个堆栈,并且您的代码将被调用,就好像它正在处理一个真正的 HTTP 请求一样,但没有启动服务器的成本。为此,我们将使用 Spring 的 MockMvc,我们可以通过在测试类上使用 @AutoConfigureMockMvc 注释来请求为我们注入它。

private MockRestServiceServer mockServer;

MockRestServiceServer 是客户端 REST 测试的主要入口点。用于涉及直接或间接使用 RestTemplate 的测试。提供一种方法来设置将通过 RestTemplate 执行的预期请求以及发送回的模拟响应,从而消除对实际服务器的需求。

mockServer.expect(once(), requestTo("www.example.com/endpoint1"))
    .andRespond(withSuccess());

您可以在此处设置模拟外部呼叫的位置。并设置预期。

this.mockMvc.perform(post("www.example2.com/example2endpoint")..

这是您实际调用您自己的端点的地方,即您在控制器中定义的端点。 Spring 将访问您的端点,执行您在控制器/服务层中拥有的所有逻辑,当涉及到实际在外部进行调用的部分时,将使用您刚刚在上面定义的 mockServer。这样,它就完全离线了。你从来没有打过实际的外部服务。此外,您将在相同的 mockMvc.perform 方法上附加您的断言。

【讨论】:

@ActiveProfiles("test"):拥有这个有什么意义,它有什么作用? @Autowired private RestTemplate restTemplate;:resttemplate 在我的客户端代码中(所以在不同的模块中)所以怎么可能自动装配呢? 通常,您会拥有一些仅用于测试的 bean。或者您可能有一些 bean 对于不同的配置文件可能会有不同的行为。所以当你输入@ActiveProfiles("test") 时,只有那些标有@Profile("test") 的bean 才会被实例化。此外,这个@ActiviveProfiles("test") 将激活测试配置文件。其次,如果您已经在其他地方定义了 restTemplate bean,那么无需担心。 Spring 会在这个类中自动为你自动装配它。如果它仍然不起作用,那么您可以提供一个具有这样的 resttemplate bean 的类名: @SpringBootTest(classes = ClassNameWhichContainsBeanDefinitionOfRestTemplate.class)) @MoNigma 试试这个,而不是自动装配它,只​​需创建一个新的 RestTemplate 实例。试试看。 我已经更新了这个问题。请看看我现在有什么,因为我现在有点困惑(我是这个使用 mockito 进行高级测试的新手)。

以上是关于使用 Mockito 在客户端测试 POST 请求的主要内容,如果未能解决你的问题,请参考以下文章

使用 Junit 和 Mockito 嵌套异常问题测试 POST Api

如何使用 mockito/powermock 模拟 Google 的地理编码 API 请求?

使用 Mockito 模拟服务器-客户端连接

Mockito 比 EasyMock 更受欢迎? [关闭]

Flutter:Mockito http客户端单元测试异常

我需要在 Spring Boot 项目中使用 mockito