Dernière modification : Dec 08 , 2024

Comment tester les API Rest avec JUNIT ?

Le test d'API Rest en Java est possible bien que souvent fastidieux.

Le coté fastidieux des tests d'API REST en Java est lié notamment au format de données utilisé dans les appels (JSON) et la complexité de l'API HTTP/ Servlet API en Java

Toutefois avec les bonnes bibliothèques Java, il est possible de simplifier cette tâche d'apparence ardue.

Les frameworks Java pour créer des tests d'API Rest

  • Rest-Assured pour créer des appels REST et des assertions élégamment
  • AssertJ pour simplifier l'écriture des assertions
  • JsonPath pour facilement naviguer dans les payload JSON
  • Awaitility pour gérer les codes asynchrones
  • Object mapper pour convertir le JSON en objet et vice-vers
  • OkHttp MockWebServer pour tester les clients REST
  • Spring test : le must-have pour tester les contrôleurs REST

Rest-Assured

Rest Assured est un framework Java permettant de simplifier l'écriture d'appels REST et de valider le résultat de l'appel.

Le framework permet :

  • construire simplement des requêtes
  • valider les réponses programmatiquement ou via l'utilisation de JSON Schema
@Test public void
    lotto_resource_returns_200_with_expected_id_and_winners() {

      when().
              get("/lotto/{id}", 5).
      then().
              statusCode(200).
              body("lotto.lottoId", equalTo(5),
                  "lotto.winners.winnerId", hasItems(23, 54));
}

AssertJ

AssertJ est un framework Java permettant de simplifier l'écriture des assertions pour vos tests unitaires.

Il est également possible d'étendre AssertJ comme avec cette bibliothèque AssertJ-JSON qui permet de plus facilement vérifier les payloads JSON.

Awaitability + AssertJ
@Test
public void testWithPollDelay() {
    await().untilAsserted(() -> assertThat(
            get("/foo.json")
                    .then()
                    .statusCode(200)
                    .extract().path("test").toString()).isEqualTo("hello0"));
}

Utilisation d'assertJ-Json.

DocumentContext ctx = JsonPath.parse("{\"value\":\"a text value\"}");

assertThat(ctx).jsonPathAsString("$.value").contains("text").endsWith("value");

JsonPath

JsonPath est un framework Java permettant de rechercher des informations dans des payload JSON. La technologie est très inspirée de XPath pour les fichiers XML.

JSONPath utilise une syntaxe particulière pour retrouver des noeuds spécifiques dans un arbre JSON.

$.store.book[0].title

ou sous la forme de maps :

$['store']['book'][0]['title']
String json = "...";
Object document = Configuration.defaultConfiguration().jsonProvider().parse(json);

String author0 = JsonPath.read(document, "$.store.book[0].author");
String author1 = JsonPath.read(document, "$.store.book[1].author");

Jackson (ObjectMapper)

Il est fortement recommandé de maîtriser l'API Jackson qui sert à sérialiser / désérialiser le JSON en classes Java et réaliser toute la partie Mapping.

En effet, les applications Spring utilisent Jackson pour la conversion des classes Java en Jackson.
Si vous détectez des problèmes dans les formats de retours (dont les dates), vous aurez à comprendre comment les développeurs utilisent Jackson.

Car car = objectMapper.readValue(new File("src/test/resources/json_car.json"), Car.class);

Awaitility

Ce framework permet de régler les situations ou vous devez tester des API Rests reactives ou qui utilisent des systèmes de callback.

De manière pratique, Awaitability permet de rendre synchrone des opérations asynchrones afin de faciliter l'écriture des tests.

  @Test
  public void updatesCustomerStatus() {
      // Publish an asynchronous message to a broker (e.g. RabbitMQ):
      messageBroker.publishMessage(updateCustomerStatusMessage);
      // Awaitility lets you wait until the asynchronous operation completes:
      await().atMost(5, SECONDS).until(customerStatusIsUpdated());
      ...
  }

MockWebServer

Cette bibliothèque est très utile si vous devez développer un service qui dépend d'une API Rest. Elle vous permet de bouchonner les appels à l'API Rest en retournant des payloads prédéfinis.

Un serveur Web mock simule le comportement d'un API Rest sans appeler directement le vrai service.

  public class EmployeeServiceMockWebServerTest {

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


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

  @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<Employee> employeeMono = employeeService.getEmployeeById(100);

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

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

Les possibilités de test avec le Framework Spring

Le framework Spring permet notamment d'écrire des services REST, réactifs ou non, utilisant différents formats de transfert de données.

Par conséquent, la bibliothèque Spring test fournit différents utilitaires pour écrire vos tests.

  • Les API WebClient et RestTemplate permettent de créer facilement un client REST
  • l'API SpringBootTest permet de démarrer votre service notamment sur un port aléatoire et un serveur embarqué
  • TestRestTemplate vous permet de faire des appels REST dans vos tests préconfigurés
  • Il est possible de recevoir le port du serveur dans votre test via l'utilisation de @LocalServerPort
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class HttpRequestTest {

  @LocalServerPort
  private int port;

  @Autowired
  private TestRestTemplate restTemplate;

  @Test
  public void greetingShouldReturnDefaultMessage() throws Exception {
    assertThat(this.restTemplate.getForObject("http://localhost:" + port + "/",
        String.class)).contains("Hello, World");
  }
}
  • Il est possible de tester uniquement la partie Rest via l'annotation @RestClientTest avec Spring Boot.

@RestClientTest est une autre annotation de Spring Boot utilisée pour tester une partie spécifique de votre application.

Comme le nom le suggère, vous pouvez l'utiliser pour tester les clients REST au sein de votre application.

@RunWith(SpringRunner.class)
@RestClientTest(UserServiceImpl.class)
@AutoConfigureWebClient(registerRestTemplate = true)
public class UserServiceTest 
{
  @Autowired
  private MockRestServiceServer server;

  @Autowired
  private UserService client;

  @Test
  public void testServiceCall() 
  {
    this.server.expect(requestTo("http://localhost:8080/users"))
          .andRespond(withSuccess("<users></users>", MediaType.TEXT_PLAIN)); 
    
    String userServiceResponse = client.testUserService();
    
    assertThat(userServiceResponse).isEqualTo("<users></users>");
  }
}

Les classes MockHttpServletRequest et RequestContextHolder permettent de tester simplement Servlets et Contrôleurs Spring classiques (MVC).

Le package org.springframework.mock.web contient un ensemble de mocks pour Spring MVC/WEB.

Il est possible de tester depuis la couche controller sans démarrer de serveurs Web.

Les appels REST sont simulés avec MockMvc.

  @SpringBootTest
  @AutoConfigureMockMvc
  public class TestingWebApplicationTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnDefaultMessage() throws Exception {
      this.mockMvc.perform(get("/")).andDo(print()).andExpect(status().isOk())
          .andExpect(content().string(containsString("Hello, World")));
    }
  }

Comment tester les API Rest avec JUNIT ?

Created by Sylvain Leroy


Documentation et resources