简体   繁体   English

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

[英]Testing POST request in client side using Mockito

I want to test the post method that should send a post request to "server" (So I want to mock the response from the server and check the response). 我想测试应该向“服务器”发送帖子请求的post方法(所以我想模拟来自服务器的响应并检查响应)。 Also, I want to test that the response contains http status OK in the body. 此外,我想测试响应在正文中包含http状态OK。 Question: How should I do that with mockito? 问题:我应该如何使用mockito?

My Post Method in the client (Client-side): 客户端中的My Post方法(客户端):

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");
        }

      }
  }

My Controller in another module (server-side): 我的控制器在另一个模块(服务器端):

@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);
    }

Test: 测试:

    @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));
    }

}

And now i am getting this error: 现在我收到这个错误:

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)}

Based on your Client class , would like to suggest below changes so as to make it better testable : 根据您的客户端类,想建议以下更改,以使其更易于测试:

    //  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;
      }
  }

And then for above we Junit as : 然后在上面我们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());
  }
}

Basically in your sendResponse() function, we are doing as: 基本上在你的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 

Thus in its corresponding test we should also test only what the function is doing since the connection is being taken care by restTemplate and you're not overriding any working of restTemplate so we should not do anything for the same... rather just test our code/logic. 因此,在相应的测试中,我们还应该只测试函数正在做什么,因为restTemplate正在处理连接,并且你没有覆盖restTemplate任何工作,所以我们不应该做同样的事情......而只是测试我们的的代码/逻辑。

Lastly, to be sure imports looks like : 最后,确保导入如下:

to be sure , imports will be like : 可以肯定的是,进口将像:

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;

Hope this helps. 希望这可以帮助。

Full code first (explanation is below): 首先是完整代码(解释如下):

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()));
   }

You would need to use @AutoConfigureMockMvc annotation. 您需要使用@AutoConfigureMockMvc注释。 The purpose behind is is to not start the server at all, but test only the layer below that, where Spring handles the incoming HTTP request and hands it off to your controller. 其背后的目的是根本不启动服务器,而是仅测试下面的层,其中Spring处理传入的HTTP请求并将其交给控制器。 That way, almost the full stack is used, and your code will be called exactly the same way as if it was processing a real HTTP request, but without the cost of starting the server. 这样,几乎使用了完整的堆栈,您的代码将被调用的方式与处理实际HTTP请求的方式完全相同,但不需要启动服务器的成本。 To do that we will use Spring's MockMvc, and we can ask for that to be injected for us by using the @AutoConfigureMockMvc annotation on the test class. 要做到这一点,我们将使用Spring的MockMvc,我们可以通过在测试类上使用@AutoConfigureMockMvc注释来请求为我们注入。

private MockRestServiceServer mockServer;

MockRestServiceServer is a main entry point for client-side REST testing. MockRestServiceServer是客户端REST测试的主要入口点。 Used for tests that involve direct or indirect use of the RestTemplate. 用于涉及直接或间接使用RestTemplate的测试。 Provides a way to set up expected requests that will be performed through the RestTemplate as well as mock responses to send back thus removing the need for an actual server. 提供一种方法来设置将通过RestTemplate执行的预期请求以及模拟响应以发回,从而消除对实际服务器的需求。

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

This is where you would setup mocking to outside calls. 您可以在此处设置模拟外部呼叫。 And setup expectations as well. 并设定期望。

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

This is where you would actually make a rest/api call to your own endpoint, the one that you defined in your controller. 这是您实际对您自己的端点(您在控制器中定义的端点)进行rest / api调用的地方。 Spring will hit your endpoint, perform all the logic that you have in your controller/service layer, and when it comes to the part of actually making a call outside, will use mockServer that you just defined above. Spring将命中你的端点,执行你在控制器/服务层中的所有逻辑,当涉及实际在外面调用的部分时,将使用你刚才定义的mockServer。 That way, it is totally offline. 这样,它完全脱机。 You never hit the actual outside service. 你从来没有打过实际的外部服务。 Also, you will append your assertions on the same mockMvc.perform method. 此外,您将在同一个mockMvc.perform方法上附加断言。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM