Тестирование POST-запроса на стороне клиента с использованием Mockito - PullRequest
1 голос
/ 16 марта 2019

Я хочу протестировать метод записи, который должен отправлять запрос на публикацию на «сервер» (поэтому я хочу смоделировать ответ с сервера и проверить ответ). Также я хочу проверить, что ответ содержит http статус OK в теле. Вопрос: Как мне это сделать с мокито?

Метод «Моя почта» в клиенте (на стороне клиента):

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

Ответы [ 2 ]

2 голосов
/ 17 марта 2019

Исходя из вашего класса Клиента, мы хотели бы предложить следующие изменения, чтобы сделать его более тестируемым:

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

Надеюсь, это поможет.

1 голос
/ 16 марта 2019

Сначала полный код (пояснение ниже):

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 является главной точкой входа для client-боковое REST-тестирование.Используется для тестов, которые предполагают прямое или косвенное использование шаблона RestTemplate.Предоставляет способ настройки ожидаемых запросов, которые будут выполняться через RestTemplate, а также фиктивные ответы для отправки обратно, что устраняет необходимость в реальном сервере.

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

Здесь вы можете настроить насмешку снаружизвонки.И настройте ожидания.

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

Здесь вы на самом деле делаете вызов rest / api к своей конечной точке, той, которую вы определили в вашем контроллере.Spring поразит вашу конечную точку, выполнит всю логику, которая есть у вас на уровне контроллера / сервиса, и когда дело доходит до части фактического совершения вызова снаружи, будет использовать mockServer, который вы только что определили выше.Таким образом, это полностью автономно.Вы никогда не попали на фактическое внешнее обслуживание.Кроме того, вы добавите свои утверждения к тому же методу mockMvc.perform.

...