Zombando de um WebClient no Spring

Zombando de um WebClient no Spring

1. Visão geral

Atualmente, esperamos chamar APIs REST na maioria dos nossos serviços. Spring oferece algumas opções para construir um cliente REST eWebClient is recommended.

Neste tutorial rápido, veremos comounit test services that use WebClient to call APIs.

2. Zombando

Temos duas opções principais para zombar em nossos testes:

3. Usando o Mockito

Mockito é a biblioteca de simulação mais comum para Java. É bom em fornecer respostas predefinidas para chamadas de método, mas as coisas ficam desafiadoras ao simular APIs fluentes. Isso ocorre porque em uma API fluente, muitos objetos passam entre o código de chamada e o mock.

Por exemplo, vamos ter uma classeEmployeeService com um métodogetEmployeeById para buscar dados via HTTP usandoWebClient:

public class EmployeeService {

    public Mono getEmployeeById(Integer employeeId) {
        return webClient
                .get()
                .uri("http://localhost:8080/employee/{id}", employeeId)
                .retrieve()
                .bodyToMono(Employee.class);
    }
}

Podemos usar o Mockito para zombar disso:

@ExtendWith(MockitoExtension.class)
public class EmployeeServiceTest {

    @Test
    void givenEmployeeId_whenGetEmployeeById_thenReturnEmployee() {

        Integer employeeId = 100;
        Employee mockEmployee = new Employee(100, "Adam", "Sandler",
          32, Role.LEAD_ENGINEER);
        when(webClientMock.get())
          .thenReturn(requestHeadersUriSpecMock);
        when(requestHeadersUriMock.uri("/employee/{id}", employeeId))
          .thenReturn(requestHeadersSpecMock);
        when(requestHeadersMock.retrieve())
          .thenReturn(responseSpecMock);
        when(responseMock.bodyToMono(Employee.class))
          .thenReturn(Mono.just(mockEmployee));

        Mono employeeMono = employeeService.getEmployeeById(employeeId);

        StepVerifier.create(employeeMono)
          .expectNextMatches(employee -> employee.getRole()
            .equals(Role.LEAD_ENGINEER))
          .verifyComplete();
    }

}

Como podemos ver, precisamos fornecer um objeto mock diferente para cada chamada na cadeia, com quatro chamadaswhen /thenReturn diferentes necessárias. This is verbose and cumbersome. Também requer que saibamos os detalhes de implementação de como exatamente nosso serviço usaWebClient,, tornando esta uma forma frágil de teste.

Como podemos escrever testes melhores paraWebClient?

4. UsandoMockWebServer

MockWebServer, construído pela equipe do Square, é um pequeno servidor web que pode receber e responder às solicitações HTTP.

Interacting with MockWebServer from our test cases allows our code to use real HTTP calls to a local endpoint. Temos o benefício de testar as interações HTTP pretendidas e nenhum dos desafios de zombar de um cliente fluente complexo.

Usando MockWebServer érecommended by the Spring Team para escrever testes de integração.

4.1. MockWebServer Dependências

Para usarMockWebServer, precisamos adicionar dependências Maven paraokhttp emockwebserver ao nosso pom.xml:


    com.squareup.okhttp3
    okhttp
    4.0.1
    test


    com.squareup.okhttp3
    mockwebserver
    4.0.1
    test

4.2. AdicionandoMockWebServer ao nosso teste

Vamos testar nossoEmployeeService comMockWebServer:

public class EmployeeServiceMockWebServerTest {

    public static MockWebServer mockBackEnd;

    @BeforeAll
    static void setUp() throws IOException {
        mockBackEnd = new MockWebServer();
        mockBackEnd.start();
    }

    @AfterAll
    static void tearDown() throws IOException {
        mockBackEnd.shutdown();
    }
}

Na classe de teste JUnit acima, o métodosetUpetearDown se encarrega de criar e encerrar oMockWebServer.

A próxima etapa émap the port of the actual REST service call to the MockWebServer’s port.

@BeforeEach
void initialize() {
    String baseUrl = String.format("http://localhost:%s",
      mockBackEnd.getPort());
    employeeService = new EmployeeService(baseUrl);
}

Agora é hora de criar um esboço para queMockWebServer possa responder a umHttpRequest.

4.3. Stubbing uma resposta

Vamos usar o métodoMockWebServer’s práticoenqueue para enfileirar uma resposta de teste no servidor da web:

@Test
void getEmployeeById() throws Exception {
    Employee mockEmployee = new Employee(100, "Adam", "Sandler",
      32, Role.LEAD_ENGINEER);
    mockBackEnd.enqueue(new MockResponse()
      .setBody(objectMapper.writeValueAsString(mockEmployee))
      .addHeader("Content-Type", "application/json"));

    Mono employeeMono = employeeService.getEmployeeById(100);

    StepVerifier.create(employeeMono)
      .expectNextMatches(employee -> employee.getRole()
        .equals(Role.LEAD_ENGINEER))
      .verifyComplete();
}

When the actual API call is made do métodogetEmployeeById(Integer employeeId) em nossa classeEmployeeService ,MockWebServer will respond with the queued stub.

4.4. Verificando uma solicitação

Também podemos querer ter certeza de queMockWebServer foi enviado oHttpRequest correto.

MockWebServer tem um método útil chamadotakeRequest que retorna uma instância deRecordedRequest:

RecordedRequest recordedRequest = mockBackEnd.takeRequest();

assertEquals("GET", recordedRequest.getMethod());
assertEquals("/employee/100", recordedRequest.getPath());

ComRecordedRequest, podemos verificar oHttpRequest que foi recebido para ter certeza de que nossoWebClient o enviou corretamente.

5. Conclusão

Neste tutorial, tentamos as duas opções principais disponíveis paramock WebClient based REST client code.

Embora o Mockito funcione e talvez seja uma boa opção para exemplos simples, a abordagem recomendada é usarMockWebServer.

Como sempre, o código-fonte deste artigo está disponívelover on GitHub.