- vừa được xem lúc

E2E testing cho ứng dụng ReactJS sử dụng Cypress

0 0 18

Người đăng: Nguyễn Danh Khương

Theo Viblo Asia

Xin chào, trong bài viết này mình sẽ viết E2E (end-to-end) testing cho ứng dụng ReactJS sử dụng Cypress

1. Cấu hình Cypress cho ứng dụng ReactJS

Mục đích chính của bài viết này là về CypressE2E testing, do đó mình đã chuẩn bị sẵn 1 ứng dụng ReactJS.
Ứng dụng gồm 2 page: /home và /products. Trong đó page products hiển thị 1 list các sản phẩm và có thể thêm, xoá sản phẩm.
Repository: https://github.com/khuongitptit/demo-cypress.
Branch start là project khi bắt đầu, còn branch end là project đã có các file e2e test mình viết để mọi người tham khảo.
Sau khi clone về, chạy yarn install, sau đó yarn dev để chạy ứng dụng.
Để cài cypress, chạy:
yarn add cypress --save Để bắt đầu sử dụng, chạy npx cypress open, cypress sẽ được tự động mở lên.
Tại cửa sổ bật lên, chọn E2E testing:
Các file config hiện ra, chọn Continue:
Đợi 1 chút và các file config sẽ tự động generate trong code của project. Ở cửa số Cypress, chọn Start E2E Testing in Chrome. Cửa sổ Chrome sẽ được tự động bật lên:
Các test sẽ hiện ở tab Specs, tuy nhiên ở đây mình chưa viết test nào. Có thể tạo file test trực tiếp trên giao diện cửa sổ Chrome của Cypress hoặc tự tạo trong code. Ở đây mình sẽ tạo luôn trên giao diện:
Scaffold example specs sẽ tạo ra 1 loạt file mẫu, tuy nhiên ở ứng dụng của mình thì sẽ hơi thừa, do đó mình sẽ tạo file trống:
Chọn Create new empty spec, nhập cypress/e2e/home.spec.cy.ts (file này mình sẽ dùng để test cho page /home). Chọn Create Spec.
Một file test sẽ được tự tạo ra trong code với nội dung:

describe('empty spec', () => { it('passes', () => { cy.visit('https://example.cypress.io') })
})

Chọn Create another spec để tạo thêm 1 file để test cho page /products. Thao tác tương tự như trên, nhập tên cypress/e2e/products.spec.cy.ts
Sau đó chọn Okay, run the spec, file test mình vừa tạo sẽ được chạy:
Như vậy là mình đã cấu hình xong Cypress để sẵn sàng viết các test

2. Viết test

Ở phần 1 mình đã 2 file test home.spec.cy.tsproducts.spec.cy.ts.
Page home của ứng dụng đơn giản là gồm 1 navbar và 1 dòng text "Hello from Cypress":
Mình sẽ tiến hành viết test cho page home trước. Sửa file home.spec.cy.ts như sau:

//cypress/e2e/home.spec.cy.ts
describe('Home page', () => { beforeEach(() => { cy.visit('http://localhost:3000') }) it('render', () => { cy.get("nav ul").should("be.visible").within(() => { cy.get("li a").should("have.length", 2) }) cy.get("div").contains("Hello from Cypress").should("be.visible") }) it('change page', () => { const linkToProductsPage = cy.get("a").contains("Products") linkToProductsPage.should("be.visible") linkToProductsPage.click() cy.url().should("include", "/products") })
})

Trong file test này, mình định nghĩa 1 hàm beforeEach. Hàm này sẽ chạy trước khi mỗi it (individual test) chạy.
Ở đây, trước mỗi test, mình sẽ truy cập vào trang home của ứng dụng: cy.visit('http://localhost:3000')
Mình chia thành 2 it.

  • it đầu tiên sẽ test phần navbar có hiển thị hay không, có đủ 2 thẻ link không và test dòng chữ Hello from Cypress có hiển thị không
  • it thứ hai sẽ test sau khi click vào link products thì ứng dụng có chuyển hướng sang page products không
    Để chạy test, ở tab Specs, click vào home.spec:
    Cypress sẽ chạy theo kịch bản mà mình đã viết ở trên có kèm theo giao diện ứng dụng khi thực hiện theo kịch bản đó.
    Chỉ cần chỉ chuột vào bước ở menu bên trái là có thể xem trạng thái của ứng dụng ở bước đó.
    Màu xanh lá tức là tất cả các test đã passed.
    Như vậy là mình đã viết xong test cho màn home, màn này tương đối đơn giản
    Tiếp theo minh sẽ viết test cho page products, sẽ phức tạp hơn một chút.
    Giao diện page trông như thế này:
    Do phần navbar giống hệt page home nên mình sẽ không test lại.
    File products.spec.cy.ts như sau:
//cypress/e2e/products.spec.cy.ts
describe('Product page', () => { beforeEach(() => { cy.visit('http://localhost:3000/products') cy.intercept({ method: "POST", url: Cypress.env("REACT_APP_BASE_API_URL") + "/products", }).as("addProductAPI"); cy.intercept({ method: "DELETE", url: Cypress.env("REACT_APP_BASE_API_URL") + "/products/**", }).as("deleteProductAPI"); cy.intercept({ method: "GET", url: Cypress.env("REACT_APP_BASE_API_URL") + "/products", }).as("getListProductAPI"); cy.wait(1000) }) it('add product', () => { cy.get("h6").contains("Add product").should("be.visible") cy.get("input[name='name']").should("be.visible") cy.get("input[name='price']").should("be.visible") cy.get("button").contains("Add").should("be.visible") const testName = "test product " + (Date.now()/1000) cy.get("input[name='name']").type(testName) cy.get("input[name='price']").type("10000") cy.get("button").contains("Add").click() cy.wait("@addProductAPI").then(({ response }) => { expect(response?.statusCode).to.eq(201); cy.wait("@getListProductAPI").then(() => { cy.wait(1000) cy.get("tr").last().within(() => { cy.get("td").first().should("contain.text", testName) }) }) }); }) it('delete product', () => { let productNameToDelete cy.get("tr").last().within(() => { cy.get("td").first().invoke("text").then(text => productNameToDelete = text) cy.get("button").contains("Delete").click() }) cy.wait("@deleteProductAPI").then(({ response }) => { expect(response?.statusCode).to.eq(200); cy.wait("@getListProductAPI").then(() => { cy.get("tr").last().within(() => { cy.get("td").first().should("not.contain.text", productNameToDelete) }) }) }); }) })

Trong file này có 1 hàm beforeEach, trong đó sẽ truy cập trang products và định nghĩa các API get list product, add productdelete products.
2 it cho 2 chức năng add productdelete product

  1. add product
    it này test dòng chữ Add product và các input nhập tên product, giá product, và nút Add có được hiển thị không
    Tiếp theo mình nhập tên và giá của product và click nút Add.
    Để kiểm tra xem product vừa add đã được hiển thị trong list chưa, mình muốn tên product phải unique để dễ phân biệt, do đó mình gán vào tên product 1 dãy số là UNIX timestamp ở thời điểm chạy test (mình gán vào biếtn testName). Như vậy thì mỗi lần mình chạy test, tên product được add sẽ khác nhau.
    Sau khi click vào nút Add, mình wait API @addProductAPI chạy xong, sau đó test statusCode của API trả về có bằng 201 hay không.
    Sau đó mình tiếp tục wait API @getListProductAPI, sau đó test xem product cuối cùng trong danh sách hiển thị có tên đúng bằng testName hay không.
  2. delete product
    it này mình sẽ xoá product ở cuối danh sách bằng cách click nút Delete.
    Trước đó mình lưu tên product sẽ bị xoá vào biến productNameToDelete
    Sau khi click nút Delete, mình wait API @deleteProductAPI, sau đó test statusCode của API trả về có bằng 200 hay không (tương tự như it add product).
    Tiếp theo mình wait API @getListProductAPI chạy xong, sau đó test xem product cuối cùng của danh sách có tên khác với productNameToDelete.
    Nếu khác tức là product đã bị xoá và test passed.

3. Kết luận

Trên đây mình đã trình bày cách viết E2E testing cho 2 page đơn giản của ứng dụng ReactJS.
Có thể thấy Cypress là 1 framework khá dễ sử dụng vì cú pháp code gần giống ngôn ngữ tự nhiên, config đơn giản mà không cần động vào code, có giao diện trực quan để xem từng bước thực hiện thao thác trên ứng dụng.
Chi tiết mọi người có thể xem thêm trên trang chủ của Cypress

Cảm ơn mọi người đã đọc bài viết!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Cùng tìm hiểu về các hook trong React hooks

Đối với ai đã từng làm việc với React thì chắc hẳn đã có những lúc cảm thấy bối rối không biết nên dùng stateless (functional) component hay là stateful component. Nếu có dùng stateful component thì cũng sẽ phải loay hoay với đống LifeCycle 1 cách khổ sở Rất may là những nhà phát triển React đã kịp

0 0 81

- vừa được xem lúc

Khi nào nên (và không nên) sử dụng Redux

. Công việc quản lý state với những hệ thống lớn và phức tạp là một điều khá khó khăn cho đến khi Redux xuất hiện. Lấy cảm hứng từ design pattern Flux, Redux được thiết kế để quản lý state trong các project JavaScript.

0 0 106

- vừa được xem lúc

ReactJS: Props và State

Nếu bạn đã học ReactJS hay React Native, bạn sẽ thấy các Props và State được sử dụng rất nhiều. Vậy chính xác chúng là gì? Làm thế nào để chúng ta sử dụng chúng đúng mục đích đây.

0 0 41

- vừa được xem lúc

State và Props trong Reactjs

Hello các bạn, tiếp tục seri tìm hiểu về ReactJs hôm nay mình xin giới thiệu đến các bạn hai thứ mình cho là thú vị nhất của ReactJs là State và Props. State bạn có thể hiểu đơn giản là một nơi mà bạn lưu trữ dữ liệu của Component, từ đó bạn có thể luân chuyển dữ liệu đến các thành phần trong Compon

0 0 36

- vừa được xem lúc

Memoization trong React

. 1.Introduction. Memoization có liên quan mật thiết đến bộ nhớ đệm, và dưới đây là một ví dụ đơn giản:. const cache = {}.

0 0 38

- vừa được xem lúc

Nâng cao hiệu suất React Hooks với React.memo, Memoization và Callback Functions

1.Ngăn Re-render và React.memo. React.

0 0 67