Bạn đã chạy Cypress cho các bài kiểm thử end-to-end (đầu cuối). Bây giờ bạn muốn truy cập trực tiếp vào một API, kiểm tra mã trạng thái và xác nhận nội dung JSON mà không cần nhấp qua giao diện người dùng. Cypress có thể làm được điều đó. Lệnh cy.request() gửi các yêu cầu HTTP thực từ Node, bên ngoài trình duyệt, vì vậy bạn có thể kiểm thử một điểm cuối riêng biệt hoặc thiết lập trạng thái trước khi một bài kiểm thử UI chạy.
Hướng dẫn này sẽ chỉ cho bạn cách kiểm thử API với Cypress: những điều cơ bản về cy.request(), xác nhận phản hồi, xâu chuỗi các yêu cầu để tái sử dụng mã thông báo xác thực và giả lập lưu lượng trình duyệt với cy.intercept(). Bạn cũng sẽ thấy Cypress-cho-API phù hợp ở đâu và khi nào một công cụ chuyên biệt cho API sẽ tốt hơn.
Tại sao nên kiểm thử API với Cypress
Hầu hết các bộ Cypress đều điều khiển trình duyệt. Nhưng phần lớn những gì bạn xác minh trong UI lại là API bên dưới: liệu /login có trả về một mã thông báo, liệu /users có trả về đúng định dạng, liệu một yêu cầu POST có tạo ra bản ghi bạn mong đợi hay không.
Kiểm thử trực tiếp các điểm cuối đó mang lại hai lợi ích. Thứ nhất, kiểm tra API chạy nhanh hơn các luồng UI vì không có việc kết xuất, không cần chờ đợi các phần tử. Thứ hai, bạn có thể sử dụng cy.request() để thiết lập dữ liệu kiểm thử. Thay vì nhấp qua biểu mẫu đăng ký để tạo người dùng, bạn gửi một yêu cầu POST và chuyển sang xác nhận mà bạn thực sự quan tâm.
Nếu Cypress đã có trong ngăn xếp công nghệ của bạn, việc thêm các bài kiểm thử API có nghĩa là không cần cài đặt công cụ mới. Bạn viết chúng trong cùng các tệp đặc tả, với cùng các xác nhận expect, trong cùng một lần chạy CI.
Những điều cơ bản về cy.request()
cy.request() tạo một yêu cầu HTTP và trả về phản hồi. Nó chạy từ tiến trình Node của Cypress, không phải trình duyệt, vì vậy nó không bị ảnh hưởng bởi các quy tắc CORS hoặc same-origin (cùng nguồn gốc).
Lệnh này chấp nhận một số dạng:
cy.request(url)
cy.request(url, body)
cy.request(method, url)
cy.request(method, url, body)
cy.request(options)
Cuộc gọi đơn giản nhất là truyền một URL. Nếu không có phương thức, Cypress mặc định là GET:
cy.request('https://jsonplaceholder.typicode.com/users')
Đối với bất kỳ điều gì ngoài một yêu cầu GET cơ bản, hãy truyền một đối tượng tùy chọn. Điều này cho phép bạn kiểm soát hoàn toàn phương thức, nội dung và tiêu đề:
cy.request({
method: 'POST',
url: 'https://jsonplaceholder.typicode.com/posts',
headers: {
'Content-Type': 'application/json'
},
body: {
title: 'API test',
body: 'created from Cypress',
userId: 1
}
})
Các tùy chọn hữu ích trong đối tượng đó:
method: động từ HTTP (GET,POST,PUT,PATCH,DELETE, và các loại khác). Mặc định làGET.url: điểm cuối. Bắt buộc.body: tải trọng yêu cầu. Cypress chuyển đổi các đối tượng thành JSON.headers: một đối tượng các tiêu đề yêu cầu.auth: thông tin xác thực cho Xác thực HTTP cơ bản (HTTP Basic Authentication).qs: các tham số chuỗi truy vấn dưới dạng đối tượng.failOnStatusCode: đặt thànhfalsekhi bạn muốn xác nhận phản hồi 4xx hoặc 5xx thay vì làm cho bài kiểm thử thất bại.
Tùy chọn cuối cùng đó quan trọng đối với các bài kiểm thử tiêu cực. Theo mặc định, cy.request() sẽ làm cho bài kiểm thử thất bại nếu gặp bất kỳ trạng thái nào không phải 2xx hoặc 3xx. Nếu bạn đang kiểm tra xem một yêu cầu không hợp lệ có trả về 400 hay không, bạn phải bỏ qua tùy chọn mặc định:
cy.request({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/users/99999',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404)
})
Xác nhận trạng thái và nội dung
cy.request() trả về một đối tượng phản hồi. Hãy xâu chuỗi .then() để đọc nó. Phản hồi có bốn thuộc tính bạn sẽ sử dụng nhiều nhất:
status: mã trạng thái HTTP.body: tải trọng phản hồi. Cypress phân tích cú pháp nó thành một đối tượng JavaScript khiContent-Typekết thúc bằngjson.headers: các tiêu đề phản hồi.duration: thời gian yêu cầu mất, tính bằng mili giây.
Đây là một bài kiểm thử hoàn chỉnh kiểm tra trạng thái, định dạng nội dung và một trường cụ thể:
describe('Users API', () => {
it('returns a list of users', () => {
cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
expect(response.status).to.eq(200)
expect(response.body).to.be.an('array')
expect(response.body).to.have.length(10)
expect(response.body[0]).to.have.property('email')
})
})
})
Vì response.body đã là một đối tượng được phân tích cú pháp, bạn xác nhận trên đó bằng Chai tiêu chuẩn. Bạn có thể kiểm tra kiểu, độ dài, các thuộc tính lồng nhau hoặc các giá trị chính xác. Đây là cùng cú pháp xác nhận bạn sử dụng cho các bài kiểm thử UI, vì vậy không có gì mới để học.
Bạn cũng có thể xác nhận trên các tiêu đề phản hồi, điều này tiện lợi cho việc kiểm tra loại nội dung hoặc bộ nhớ đệm:
cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
expect(response.headers['content-type']).to.include('application/json')
})
Để tìm hiểu sâu hơn về cách cấu trúc các kiểm tra này, hãy xem hướng dẫn của chúng tôi về xác nhận API và các phương pháp hay nhất về kiểm thử API.
Xâu chuỗi yêu cầu và tái sử dụng mã thông báo xác thực
Các API thực tế cần xác thực. Mẫu chung là đăng nhập một lần, lấy mã thông báo, sau đó gửi nó trong mỗi yêu cầu tiếp theo. cy.request() xâu chuỗi gọn gàng cho điều này.
Gửi yêu cầu đăng nhập, đọc mã thông báo từ nội dung phản hồi, sau đó sử dụng nó trong lần gọi tiếp theo:
describe('Authenticated API flow', () => {
it('logs in and fetches a protected resource', () => {
cy.request({
method: 'POST',
url: 'https://api.example.com/login',
body: {
email: 'user@example.com',
password: 'secret'
}
}).then((loginResponse) => {
expect(loginResponse.status).to.eq(200)
const token = loginResponse.body.token
cy.request({
method: 'GET',
url: 'https://api.example.com/profile',
headers: {
Authorization: `Bearer ${token}`
}
}).then((profileResponse) => {
expect(profileResponse.status).to.eq(200)
expect(profileResponse.body).to.have.property('email', 'user@example.com')
})
})
})
})
```Nếu một số bài kiểm thử cần cùng một mã thông báo, hãy chuyển phần đăng nhập vào một beforeEach và lưu trữ mã thông báo với Cypress.env() hoặc một alias (bí danh) được bao bọc để mỗi bài kiểm thử có thể lấy nó. Điều này giữ thiết lập xác thực ở một nơi thay vì lặp lại nó trong mỗi đặc tả.
Mẫu đăng nhập rồi sử dụng này có cùng hình dạng mà bạn sẽ viết trong bất kỳ luồng kiểm thử tích hợp API nào, nơi một cuộc gọi cung cấp dữ liệu cho cuộc gọi tiếp theo.
cy.intercept() để giả lập (stubbing)
cy.request() truy cập vào API thực tế. Đôi khi bạn muốn điều ngược lại: chặn các yêu cầu mà ứng dụng front-end của bạn thực hiện và trả về một phản hồi giả mạo. Đó là công dụng của cy.intercept().
Có một sự khác biệt quan trọng ở đây. cy.intercept() chỉ chặn các yêu cầu được thực hiện bởi ứng dụng front-end của bạn trong trình duyệt. Nó không chặn cy.request(), bởi vì cy.request() bỏ qua hoàn toàn trình duyệt. Sử dụng cy.intercept() khi bạn đang kiểm thử cách UI của bạn phản ứng với một phản hồi API nhất định, chứ không phải khi bạn đang kiểm thử chính API đó.
Bạn có thể theo dõi một yêu cầu, giả lập nó với một phản hồi tĩnh, hoặc đợi nó. Để giả lập, hãy truyền một đối tượng phản hồi:
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [{ id: 1, name: 'Ada' }]
})
Bây giờ, bất kỳ yêu cầu trình duyệt nào đến /api/users sẽ trả về tải trọng cố định đó. Điều này cho phép bạn kiểm thử các trường hợp biên khó kích hoạt với một backend trực tiếp, như danh sách trống, lỗi 500 hoặc phản hồi chậm.
Để xác nhận rằng yêu cầu đã xảy ra, hãy đặt bí danh cho nó bằng .as() và đợi nó:
it('shows an error banner when the API fails', () => {
cy.intercept('GET', '/api/users', {
statusCode: 500,
body: { message: 'Server error' }
}).as('getUsers')
cy.visit('/dashboard')
cy.wait('@getUsers').its('response.statusCode').should('eq', 500)
cy.contains('Something went wrong').should('be.visible')
})
```Dòng cy.wait('@getUsers') tạm dừng bài kiểm thử cho đến khi yêu cầu bị chặn được giải quyết, sau đó cung cấp cho bạn yêu cầu và phản hồi để xác nhận. Thiết lập việc chặn trước hành động kích hoạt yêu cầu, nếu không nó sẽ không bắt được bất cứ điều gì. Nếu bạn đang cân nhắc khi nào nên giả mạo một phản hồi so với giả lập toàn bộ một dịch vụ, các bài viết của chúng tôi về giả lập API (API mocking) và giả lập API (API stubbing) so với giả lập API (API mocking) sẽ phân tích các đánh đổi.
Khi nào Cypress-cho-API có ý nghĩa, và khi nào không
Cypress rất phù hợp cho các kiểm tra API nằm trong một bộ kiểm thử UI. Nếu bạn đã viết các bài kiểm thử e2e và cần gieo dữ liệu, xác minh một điểm cuối mà UI của bạn phụ thuộc vào, hoặc giả lập một phản hồi để kiểm thử xử lý lỗi, cy.request() và cy.intercept() sẽ giữ mọi thứ ở một nơi. Không cần chuyển đổi ngữ cảnh, không cần công cụ thứ hai.
Nó cũng tốt cho một vài bài kiểm thử API độc lập (smoke tests) khi Cypress là công cụ chạy kiểm thử duy nhất của bạn và bạn không muốn thêm một phụ thuộc nào khác.
Nó trở nên khó khăn khi có một bộ kiểm thử API độc lập, quy mô lớn. Cypress được xây dựng cho trình duyệt, vì vậy kiểm thử API là một khả năng được thêm vào, chứ không phải là thiết kế cốt lõi. Một vài lỗ hổng xuất hiện khi bộ kiểm thử phát triển:
Không có trình tạo yêu cầu trực quan. Mọi yêu cầu đều là mã. Việc viết tiêu đề, nội dung và tham số truy vấn bằng tay sẽ chậm khi bạn có hàng trăm điểm cuối.Yếu hơn đối với một pipeline API chỉ dành cho CI. Cypress khởi động một ngữ cảnh trình duyệt ngay cả đối với các lần chạy API thuần túy, điều này là một chi phí không cần thiết.Không có định nghĩa API dùng chung. Các định dạng yêu cầu nằm trong các tệp kiểm thử của bạn, chứ không phải trong một đặc tả mà nhóm của bạn có thể tái sử dụng trong thiết kế, tài liệu và kiểm thử.
Đây là lúc một công cụ chuyên biệt cho API phù hợp hơn. Apidog được xây dựng xung quanh chính API: bạn thiết kế một điểm cuối một lần, sau đó xây dựng các kịch bản kiểm thử dựa trên nó với một trình tạo yêu cầu trực quan và các xác nhận trực quan, không yêu cầu mã cho các trường hợp phổ biến. Các yêu cầu, môi trường và xác thực nằm trong một không gian làm việc chung, vì vậy nhóm của bạn không cần phải suy ra lại chúng từ các tệp kiểm thử.
Đối với CI, Apidog CLI chạy các kịch bản kiểm thử đã lưu của bạn mà không cần giao diện đồ họa (headlessly) trong bất kỳ bước nào có thể chạy Node:
npm install -g apidog-cli
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t <scenarioOrSuiteId> \
-e <environmentId> \
-r cli,html,junit
Cờ -t nhắm mục tiêu đến một kịch bản hoặc bộ đã lưu, -e chọn một môi trường và -r chọn định dạng báo cáo (cli, html, json, junit). Đầu ra JUnit tích hợp trực tiếp vào hầu hết các bảng điều khiển CI. Thêm -d hoặc --iteration-data để chạy kiểm thử từ một tệp dữ liệu, và --upload-report để đẩy kết quả trở lại không gian làm việc của bạn. CLI chạy các kịch bản đã lưu; nó không phải là một công cụ gửi yêu cầu tương tác.
Một cách hữu ích để suy nghĩ về điều này: sử dụng cy.request() cho các kiểm tra API hỗ trợ các bài kiểm thử trình duyệt của bạn, và tìm đến một công cụ chuyên biệt cho API khi kiểm thử API là công việc chính. Nếu bạn đang so sánh các tùy chọn, các bài tổng hợp của chúng tôi về các công cụ tự động hóa kiểm thử API chạy trong CI/CD và 30 công cụ kiểm thử API tốt nhất sẽ bao quát lĩnh vực này.
Câu hỏi thường gặp
cy.request() có chạy trong trình duyệt không?
Không. cy.request() chạy từ tiến trình Node của Cypress, bên ngoài trình duyệt. Đó là lý do tại sao nó không bị chặn bởi chính sách CORS hoặc same-origin, và tại sao cy.intercept() không thể chặn nó. Nếu bạn cần một yêu cầu mà trình duyệt thực hiện, hãy kích hoạt nó thông qua ứng dụng của bạn và bắt nó bằng cy.intercept().
Làm thế nào để kiểm thử phản hồi 400 hoặc 404 mà không làm thất bại bài kiểm thử?
Theo mặc định, cy.request() sẽ làm thất bại bài kiểm thử nếu gặp bất kỳ trạng thái nào không phải 2xx hoặc 3xx. Đặt failOnStatusCode: false trong đối tượng tùy chọn, sau đó tự xác nhận trạng thái bằng expect(response.status).to.eq(404).
Tôi có thể sử dụng cy.request() để đăng nhập trước một bài kiểm thử UI không?
Có, và đó là một mẫu phổ biến. Gửi một yêu cầu POST đến điểm cuối đăng nhập của bạn bằng cy.request(), đọc mã thông báo từ response.body, và lưu trữ nó (trong Cypress.env(), localStorage, hoặc một cookie) để bài kiểm thử UI của bạn bắt đầu trong trạng thái đã xác thực. Điều này bỏ qua biểu mẫu đăng nhập và tăng tốc bộ kiểm thử.
Sự khác biệt giữa cy.request() và cy.intercept() là gì?
cy.request() gửi một yêu cầu HTTP thực và trả về phản hồi thực tế, vì vậy bạn sử dụng nó để kiểm thử một API. cy.intercept() theo dõi hoặc giả mạo các yêu cầu mà front-end của bạn thực hiện trong trình duyệt, vì vậy bạn sử dụng nó để kiểm soát những gì UI của bạn nhận được. Một cái truy cập mạng; cái kia định hình nó.
Tôi nên sử dụng Cypress hay một công cụ chuyên biệt cho kiểm thử API?
Sử dụng Cypress khi các kiểm tra API hỗ trợ một bộ kiểm thử trình duyệt mà bạn đã chạy. Đối với một bộ API độc lập lớn, các pipeline API chỉ dành cho CI, hoặc một định nghĩa API dùng chung mà toàn bộ nhóm của bạn làm việc cùng, một công cụ chuyên biệt cho API như Apidog sẽ phù hợp hơn. Hãy xem các phương pháp hay nhất về kiểm thử API của chúng tôi để biết cách quyết định.
