Test NodeJS application với Jest và SuperTest (still updating)

Trong một ứng dụng NodeJS, việc test các chức năng để đảm bảo kết quả đúng trong dự án là một điều rất quan trọng. Jest được Facebook tạo nên để phục vụ cho mục đích đó. Jest là thư viện của NodeJS cho phép các developer viết test case để test các trường hợp trong ứng dụng một cách dễ dàng, ngoài ra trong trường hợp trước khi deploy lên server cần kiểm tra lại một lượt cũng không cần thiết phải test các chức năng bằng tay, việc này giúp tiết kiệm thời gian, công sức và nâng cao chất lượng sản phẩm.

1. Cài đặt

Để cài đặt Jest, chạy câu lệnh npm install –-save-dev jest vào project cần test

2. Chạy test đầu tiên trong Jest

Tất cả các file test trong Jest đều phải có đuôi là *.test.js. Mặc định, Jest sẽ test toàn bộ tất cả các file có đuôi là .test.js trong tất cả các thư mục. Tuy nhiên nên tạo ra một thư mục tests để chứa tất cả các test case như sau

Figure 1: Thư mục test để chưa test case

Thường có 2 loại test cơ bản

  • Feature test: Test một request từ đầu đến cuối, bao gồm các service chạy bên trong và các service có liên quan đến nhau. Kết quả test là kết quả sau khi chạy tất cả service
  • Units: Test các service hoặc các function độc lập, không liên quan gì đến nhau

Để chạy được bài test đầu tiên, cần viết như sau:

Figure 2: Bài test cơ bản trong Jest

Describe sẽ tạo ra test title, trong mỗi phần describe sẽ có nhiều hàm test() con, mỗi test() là một mục cần test, được đánh dấu bằng dấu (tick) nếu như test thành công, sử dụng dấu (X) nếu test thất bại (Hình 4)

Sử dụng toBe() để kiểm tra 2 giá trị cần test và nhận được phải giống nhau đúng tuyệt đối

Tạo một script mới để test Jest application

Figure 3: Testing Jest với option verbose và môi trường test

Khi chạy test, kết quả thu được như ở hình dưới. Bật option verbose cho phép nhìn được test đang chạy là test nào và đang test tới đâu và tổng thời gian chạy cho từng test là bao lâu

Figure 4: Test passed

Trong trường hợp test thất bại, Jest có chỉ ra giá trị thật (received) và giá trị mong muốn (expect) là giá trị nào

Figure 5: Test fail, có phần expected và received

Chú ý dùng try catch để bắt lỗi xảy ra trong quá trình viết test case. Chi tiết về try catch sẽ ở phần sau

3. Áp dụng Jest vào dự án

Để áp dụng Jest vào dự án thì cần cài đặt Superagent. Superagent là một thư viện cho phép người dùng tạo ra http request dưới các dạng get, post, put, delete để kiểm tra endpoint của api.

3.1. Cấu hình superagent

Superagent sẽ chạy cùng port với ứng dụng chính. Cài đặt superagent ở đây:

Figure 6: Cài đặt superagent

Chi tiết superagent tại đây: https://www.npmjs.com/package/superagent

Để không phải viết domain và port ở mỗi lần chạy test (như ở dưới, chỉ cần api) thì sử dụng superagent-prefix. Cài đặt tại đây

Figure 7: Superagent prefix

Doc cho superagent-prefix tại đây: https://github.com/johntron/superagent-prefix

Trước khi chạy test cần start server node ở môi trường dev. Sau đó chạy test case trên cùng port với môi trường dev.

Bootup dev server

Chạy test song song với dev server. Khi có thay đổi trong code, cần stop server dev rồi start lại server dev và chạy lại test. (Ví dụ này sau khi đã viết thành công test case)

Chạy test song song với dev server

Để tránh phải viết các hàm superagent.get()superagent.post(), v.v. liên tục có sử dụng accept json hoặc content-type vào tất cả các test thì đinh nghĩa các method chung trong file method, khi cần chỉ việc gọi ra method. Ví dụ khi chạy ở dưới như sau

Figure 8: Sử dụng superagent prefix

Khi sử dụng superagent-prefix thì các test sẽ được tự động thêm prefix thông qua method use(prefix). Các url để test trong superagent đều phải có '/' đằng trước

Cuối cùng để jest.setTimeout(30000) để tránh vấn đề jest bị timeout trong quá trình chạy test (có thể do api gọi quá lâu)

3.2. Một số method thông dụng

  • expect(actual).toBe(expected): Kiểm tra xem giá trị actual có bằng expected hay không.
  • expect(actual).toHaveLength(expected): Kiểm tra số lượng phần tử actual có bằng expected hay không (áp dụng cho mảng, chuỗi)
  • expect(actual).toHaveProperty(property, valueExpected?): Kiểm tra actual có chứa key property hay không, nếu có thì giá trị có bằng "valueExpected" hay không
  • expect(actual).toBeFalsy(): Kiểm tra "actual" có false hay không

Xem thêm tại: https://jestjs.io/docs/expect

3.3. Xử lí test thành công hoặc thất bại

Trong hầu hết trường hợp thì test sẽ chạy vào điều kiện thành công (tức là trả status là 200, nhưng cũng có trường hợp bắt buộc test phải trả ra lỗi xử lí thế nào)

Tất cả các test đều cần được wrap bằng try-catch, để xử lí trong trường hợp test case thành công và xử lí cả trong trường hợp viết test case bị lỗi thì hiển thị lỗi ra ngoài

Figure 9: Test case thành công, expect() được viết trong phần try và throw Error để bắt lỗi trong trường hợp test case viết sai

Tất cả các test case mà không có status bằng 2xx được tính là lỗi. Khi lỗi xảy ra cần catch error đó và expect toBe() trong catch như hình ở dưới. Hàm query(params) cho phép người dùng truyền queryString vào url.

Figure 10: Mẫu test case bắt trường hợp không đăng nhập mà vẫn vào làm, status code trả về 401 # 2xx, vì thế phải bắt ở catch {…}

3.4. Các trường hợp test đặc biệt

Một số trường hợp test đặc biệt bao gồm: phải đăng nhập và có access_token gắn trong header mới tiến hành make request được thì xử lí ra sao

Lúc đó cách tốt nhất là truyền viết 1 hàm tên là authenticated() và truyền callback vào trong hàm authenticated để set Authorization token cho người dùng

Figure 11: Set authorization token cho request sử dụng authenticated(callback)

Để set authorization token cho request thì cần login sử dụng đúng credentials. Sau đó sẽ lấy access token cho người dùng

Để tránh phải login quá nhiều lần khi gọi authenticated(), cần check userAccessToken trước đó, nếu chưa đăng nhập thì đăng nhập, còn lại thì lưu accessToken vào userAccessToken

Sử dụng authenticated() bằng cách wrap hàm authenticated() vào request mình cần gọi. Chi tiết như ở dưới

Figure 12: Sử dụng authenticated() với request get() để vào /cdp/sources với tư cách đã đăng nhập

Có thể mở rộng case này cho các trường hợp khác ví dụ như check quyền người dùng, v.v