Test REST Web Service đơn giản hơn với REST Assured

Bài viết được sự cho phép của tác giả Giang Phan 

Ở bài viết trước, tôi đã giới thiệu với các bạn cách test REST API trong Jersey project. Giả sử bây giờ chúng ta cần sử dụng API của bên thứ 3 (không phải source code trong jersey project của chúng ta), khi đó ta không thể sử dụng Jersey Test. Khi đó, chúng ta cần một thư viện khác có thể giúp chúng ta gửi một request thật để verfiy kết quả trả về.

Trong bài này tôi sẽ giới thiệu với các bạn một thư viện rất mạnh mẽ để test web service, đó chính là REST Assured. Nó cho phép chúng ta gửi một HTTP request thật và verify trên kết quả trả về một cách dễ dàng. Chẳng hạn chúng ta có một request http://localhost:8080/lotto/{id} và server trả về response JSON như sau:

Test REST Web Service
Test REST Web Service

Khi đó chúng ta dễ dàng sử dụng REST Assured để verify mọi thứ từ response trên như sau:

Test REST Web Service
Test REST Web Service

Mọi thứ trông thật đơn giản phải không nào? Bây giờ, chúng ta hãy bắt đầu cài đặt thư viện cần thiết và viết một vài test case cho REST API sử dụng REST Assured.

Cài đặt REST Assured

Để sử dụng REST Assured chúng ta đơn giản chỉ việc thêm dependency sau trong file pom.xml.

<!-- https://mvnrepository.com/artifact/io.rest-assured/rest-assured -->
<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <version>4.0.0</version>
</dependency>

  API là gì? Các nguyên tắc xây dựng Rest API

  Dùng Vim làm REST API client

Xây dựng Jersey Rest API

Bước này chỉ là giả định bên thứ ba cung cấp cho chúng ta các API để sử dụng, nếu các bạn đã có sẵn REST API, các bạn có thể đi đến bước kế tiếp “Test REST API sử dụng REST Assured”.

Để dễ hiểu, tôi sử dụng lại service đã tạo ở bài trước để hướng dẫn các bạn viết JUnit Test cho REST API với REST Assured.

Chúng ta có các API sau:

Test REST Web Service
Test REST Web Service

Giả sử tất cả các API này user cần phải được chứng thực trước. Tôi sẽ sử dụng Basic Authentication với JWT. Chi tiết các bạn xem lại bài viết “JWT – Token-based Authentication trong Jersey 2.x“.

Chúng ta có class AuthService như sau:

package com.gpcoder.api;

import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.gpcoder.helper.JwTokenHelper;
import com.gpcoder.model.User;
import com.gpcoder.service.UserService;

@Path("/auth")
public class AuthService {

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response authenticateUser(@FormParam("username") String username, @FormParam("password") String password) {

        // Authenticate the user using the credentials provided
        UserService userService = new UserService();
        User user = userService.getUser(username);
        if (user == null || !user.getPassword().equals(password)) {
            return Response.status(Response.Status.FORBIDDEN) // 403 Forbidden
                    .entity("Wrong username or password") // the response entity
                    .build();
        }

        // Issue a token for the user
        String token = JwTokenHelper.createJWT(user);

        // Return the token on the response
        return Response.ok(token).build();
    }
}

Class OrderService cần thêm Annotation Authorized của Jersey @RolesAllowed(Role.ROLE_ADMIN)

package com.gpcoder.api;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.SecurityContext;

import com.gpcoder.model.Order;
import com.gpcoder.model.Role;

// URI:
// http(s)://<domain>:(port)/<YourApplicationName>/<UrlPattern in web.xml>/<path>
// http://localhost:8080/api/orders
@Path("/orders")
@RolesAllowed(Role.ROLE_ADMIN)
public class OrderService {

    @GET
    @Path("/{id}")
    public Response get(@PathParam("id") int id) {
        Order order = new Order();
        order.setId(1);
        order.setName("gpcoder");
        return Response.ok(order).build();
    }

    @POST
    public Response insert(Order order, @Context SecurityContext securityContext) {
        int newOrderId = 999;
        return Response.status(Status.CREATED).entity(newOrderId).build();
    }

    @PUT
    @Path("/{id}")
    public Response update(@PathParam("id") int id, Order order) {
        return Response.ok().build();
    }

    @DELETE
    @Path("/{id}")
    public Response delete(@PathParam("id") int id) {
        return Response.status(Status.NO_CONTENT).build();
    }
}

Test REST API sử dụng REST Assured

Do các API cần phải được chứng thực user trước khi sử dụng, nên chúng ta cần chứng thực trước để nhận một Token (JWT), sau đó gửi chúng vào header ở mỗi request.

  • getBearerToken() : gửi request authentication để lấy token (JWT).
  • testGetById_Way1() : sử dụng để test GET /api/orders/{id}, trong ví dụ này tôi sẽ verify trực tiếp response thông qua phương thức body của REST Assured.
  • testGetById_Way2() : sử dụng để test GET /api/orders/{id}, trong ví dụ này tôi sẽ tạo một POJO object để nhận response và verify kết quả thông qua các phương thức của JUnit.
  • testCreate() : sử dụng để test POST /api/orders
  • testUpdate() : sử dụng để test PUT /api/orders/{id}
  • testDelete() : sử dụng để test DELETE /api/orders/{id}

Xem thêm các việc làm REST API hấp dẫn trên TopDev

OrderServiceRestAssuredTest.java

package com.gpcoder.api;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

import javax.ws.rs.core.Response.Status;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.gpcoder.model.Order;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;

public class OrderServiceRestAssuredTest {

    private static String BEARER_TOKEN = "";

    @BeforeClass
    public static void setUp() {
        RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
        RestAssured.baseURI = "http://localhost";
        RestAssured.port = 8080;
        BEARER_TOKEN = getBearerToken();
    }

    public static String getBearerToken() {
        Response response = 
            given()
                .header("Content-Type", "application/x-www-form-urlencoded")
                .formParam("username", "gpcoder")
                .formParam("password", "gpcoder")
                .request()
            .when()
                .post("/api/auth")
            .then()
                .assertThat()
                .statusCode(Status.OK.getStatusCode())
                .extract()
                .response();
        return response.asString();

    }

    @Test
    public void testGetById_Way1() {
        given()
             .headers("Authorization", "Bearer " + BEARER_TOKEN)
             .accept(ContentType.JSON)
        .when()
             .get("/api/orders/1")
        .then()
             .statusCode(Status.OK.getStatusCode())
             .contentType(ContentType.JSON)
             .body("id", equalTo(1)) // Verify JSON Path
             .body("name", equalTo("gpcoder"));
    }

    @Test
    public void testGetById_Way2() {
        Order order = 
            given()
                .headers("Authorization", "Bearer " + BEARER_TOKEN)
                .accept(ContentType.JSON)
            .when()
                .get("/api/orders/1")
            .then()
               .statusCode(Status.OK.getStatusCode())
               .contentType(ContentType.JSON)
               .extract().as(Order.class); // Use POJOs and Object Mapping

        Assert.assertEquals(1, order.getId().intValue());
        Assert.assertEquals("gpcoder", order.getName());
    }

    @Test
    public void testCreate() {
        Order order = new Order(1, "gpcoder");

        Integer newOrderId =
            given()
                .headers("Authorization", "Bearer " + BEARER_TOKEN)
                .contentType(ContentType.JSON)
                .accept(ContentType.JSON)
                .body(order)
           .when()
                .post("/api/orders")
           .then()
               .assertThat()
               .statusCode(Status.CREATED.getStatusCode())
               .extract().as(Integer.class);

        Assert.assertEquals(999, newOrderId.intValue());
    }

    @Test
    public void testUpdate() {
        Order order = new Order(1, "gpcoder edited");
        given()
            .headers("Authorization", "Bearer " + BEARER_TOKEN)
            .contentType(ContentType.JSON)
            .body(order)
        .when()
            .put("/api/orders/{id}/", order.getId())
        .then()
            .assertThat()
            .statusCode(Status.OK.getStatusCode());
    }

    @Test
    public void testDelete() {
        given()
            .headers("Authorization", "Bearer " + BEARER_TOKEN)
        .when()
            .delete("/api/orders/{id}/", 1)
        .then()
           .assertThat()
           .statusCode(Status.NO_CONTENT.getStatusCode());
    }
}

Các bạn có thể chạy thử để xem kết quả.

Lưu ý: Phải start server REST API lên trước khi chạy Unit Test.

Như bạn thấy, REST Assured hỗ trợ cú pháp rất quen thuộc từ Behavior-Driven Development (BDD) khi viết Unit Test là Given/ When/ Then. Nhờ vậy mà Unit test của chúng ta rất dễ đọc và dễ kiểm tra tất cả mọi thứ (setup, execution, and verification) với chỉ vài dòng code.

  • Given: Thiết lập các điều kiện cần thiết: khởi tạo các đối tượng, xác định tài nguyên cần thiết, xây dựng các dữ liệu giả, …
  • When: Triệu gọi các phương thức cần kiểm tra.
  • Then: Kiểm tra sự hoạt động đúng đắn của các phương thức.

Test REST Web Service

Trên đây là một số ví dụ cơ bản về viết Unit Test cho REST web server sử dụng thư viện REST Assured, còn rất nhiều phương thức khác hỗ trợ chúng ta viết test dễ dàng hơn và nhanh chóng hơn, các bạn hãy tham khảo thêm trên document của REST Assured.

Bài viết gốc được đăng tải tại gpcoder.com