คุณใช้ Cypress สำหรับการทดสอบแบบ end-to-end อยู่แล้ว ตอนนี้คุณต้องการเรียกใช้ API โดยตรง ตรวจสอบรหัสสถานะ และยืนยันข้อมูล JSON โดยไม่ต้องคลิกผ่าน UI Cypress สามารถทำเช่นนั้นได้ คำสั่ง cy.request() ส่งคำขอ HTTP จริงจาก Node ภายนอกเบราว์เซอร์ เพื่อให้คุณสามารถทดสอบปลายทางได้ด้วยตัวเอง หรือตั้งค่าสถานะก่อนการทดสอบ UI จะทำงาน
คู่มือนี้จะแสดงวิธีทดสอบ API ด้วย Cypress: พื้นฐานของ cy.request() การยืนยันการตอบกลับ การเชื่อมโยงคำขอเพื่อใช้โทเค็นการยืนยันตัวตนซ้ำ และการจำลองการรับส่งข้อมูลของเบราว์เซอร์ด้วย cy.intercept() คุณจะได้เห็นด้วยว่า Cypress สำหรับ API เหมาะสมกับงานใด และเครื่องมือที่รองรับ API โดยเฉพาะเหมาะกับงานใดมากกว่า
เหตุใดจึงต้องทดสอบ API ด้วย Cypress
ชุด Cypress ส่วนใหญ่ขับเคลื่อนเบราว์เซอร์ แต่สิ่งที่ตรวจสอบใน UI จำนวนมากคือ API ที่อยู่เบื้องหลัง: `/login` คืนค่าโทเค็นหรือไม่, `/users` คืนค่าในรูปแบบที่ถูกต้องหรือไม่, คำสั่ง `POST` สร้างเรคคอร์ดที่คุณคาดหวังหรือไม่
การทดสอบปลายทางเหล่านั้นโดยตรงมีประโยชน์สองประการ ประการแรก การตรวจสอบ API ทำงานเร็วกว่าขั้นตอน UI เนื่องจากไม่มีการเรนเดอร์ ไม่ต้องรอองค์ประกอบต่างๆ ประการที่สอง คุณสามารถใช้ cy.request() เพื่อตั้งค่าข้อมูลทดสอบ แทนที่จะคลิกผ่านแบบฟอร์มลงทะเบียนเพื่อสร้างผู้ใช้ คุณเพียงแค่ส่ง POST หนึ่งครั้ง แล้วไปยังการยืนยันที่คุณสนใจจริงๆ
หาก Cypress อยู่ในระบบของคุณแล้ว การเพิ่มการทดสอบ API หมายถึงไม่ต้องติดตั้งเครื่องมือใหม่ คุณเขียนโค้ดเหล่านั้นในไฟล์สเปกเดียวกัน ด้วยการยืนยัน expect แบบเดียวกัน ในการรัน CI เดียวกัน
พื้นฐานของ cy.request()
cy.request() สร้างคำขอ HTTP และส่งคืนการตอบกลับ มันทำงานจากกระบวนการ Cypress Node ไม่ใช่จากเบราว์เซอร์ ดังนั้นจึงไม่อยู่ภายใต้กฎ CORS หรือ same-origin
คำสั่งนี้รองรับหลายรูปแบบ:
cy.request(url)
cy.request(url, body)
cy.request(method, url)
cy.request(method, url, body)
cy.request(options)
การเรียกที่ง่ายที่สุดคือการส่งผ่าน URL หากไม่มีเมธอด Cypress จะตั้งค่าเริ่มต้นเป็น GET:
cy.request('https://jsonplaceholder.typicode.com/users')
สำหรับการดำเนินการนอกเหนือจาก GET พื้นฐาน ให้ส่งออบเจกต์ตัวเลือก (options object) ซึ่งจะช่วยให้คุณควบคุมเมธอด บอดี้ และส่วนหัวได้อย่างเต็มที่:
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
}
})
ตัวเลือกที่เป็นประโยชน์ในออบเจกต์นั้น:
method: HTTP verb (GET,POST,PUT,PATCH,DELETEและอื่นๆ) ค่าเริ่มต้นคือGETurl: ปลายทาง (endpoint) จำเป็นต้องระบุbody: เพย์โหลดคำขอ (request payload) Cypress จะแปลงออบเจกต์เป็น JSONheaders: ออบเจกต์ของส่วนหัวคำขอ (request headers)auth: ข้อมูลรับรองสำหรับการยืนยันตัวตน HTTP Basicqs: พารามิเตอร์สตริงคำค้น (query string parameters) ในรูปแบบออบเจกต์failOnStatusCode: ตั้งค่าเป็นfalseเมื่อคุณต้องการยืนยันการตอบกลับสถานะ 4xx หรือ 5xx แทนที่จะทำให้การทดสอบล้มเหลว
ตัวเลือกสุดท้ายนี้มีความสำคัญสำหรับการทดสอบเชิงลบ โดยค่าเริ่มต้น cy.request() จะทำให้การทดสอบล้มเหลวหากสถานะไม่ใช่ 2xx หรือ 3xx หากคุณกำลังตรวจสอบว่าคำขอที่ไม่ถูกต้องส่งคืน 400 คุณต้องเลือกที่จะปิดใช้งาน:
cy.request({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/users/99999',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404)
})
การยืนยันสถานะและบอดี้
cy.request() จะส่งคืนออบเจกต์ตอบกลับ เชื่อมโยงด้วย .then() เพื่ออ่านค่า การตอบกลับมีสี่คุณสมบัติที่คุณจะใช้บ่อยที่สุด:
status: รหัสสถานะ HTTPbody: เพย์โหลดการตอบกลับ (response payload) Cypress จะแยกวิเคราะห์เป็นออบเจกต์ JavaScript เมื่อContent-Typeลงท้ายด้วยjsonheaders: ส่วนหัวการตอบกลับduration: ระยะเวลาที่คำขอใช้ไป หน่วยเป็นมิลลิวินาที
นี่คือการทดสอบฉบับเต็มที่ตรวจสอบสถานะ รูปแบบบอดี้ และฟิลด์เฉพาะ:
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')
})
})
})
เนื่องจาก response.body เป็นออบเจกต์ที่ถูกแยกวิเคราะห์แล้ว คุณจึงสามารถยืนยันได้ด้วย Chai มาตรฐาน คุณสามารถตรวจสอบประเภท ความยาว คุณสมบัติที่ซ้อนกัน หรือค่าที่แน่นอน นี่คือไวยากรณ์การยืนยันแบบเดียวกับที่คุณใช้สำหรับการทดสอบ UI ดังนั้นจึงไม่มีอะไรใหม่ให้เรียนรู้
คุณยังสามารถยืนยันส่วนหัวการตอบกลับได้ ซึ่งมีประโยชน์สำหรับการตรวจสอบประเภทเนื้อหาหรือการแคช:
cy.request('https://jsonplaceholder.typicode.com/users').then((response) => {
expect(response.headers['content-type']).to.include('application/json')
})
สำหรับข้อมูลเชิงลึกเกี่ยวกับการจัดโครงสร้างการตรวจสอบเหล่านี้ โปรดดูคู่มือของเราเกี่ยวกับ การยืนยัน API และ แนวทางปฏิบัติที่ดีที่สุดในการทดสอบ API ที่ครอบคลุมมากขึ้น
การเชื่อมโยงคำขอและการใช้โทเค็นการยืนยันตัวตนซ้ำ
API จริงจำเป็นต้องมีการยืนยันตัวตน รูปแบบทั่วไปคือการเข้าสู่ระบบหนึ่งครั้ง ดึงโทเค็น แล้วส่งไปพร้อมกับทุกคำขอที่ตามมา cy.request() สามารถเชื่อมโยงได้อย่างราบรื่นสำหรับสิ่งนี้
ส่งคำขอเข้าสู่ระบบ อ่านโทเค็นจากบอดี้การตอบกลับ แล้วนำไปใช้ในการเรียกถัดไป:
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')
})
})
})
})
หากการทดสอบหลายรายการต้องการโทเค็นเดียวกัน ให้ย้ายการเข้าสู่ระบบไปยัง beforeEach และเก็บโทเค็นด้วย Cypress.env() หรือ alias ที่ห่อหุ้มไว้ เพื่อให้การทดสอบแต่ละรายการสามารถนำไปใช้ได้ สิ่งนี้ช่วยให้การตั้งค่าการยืนยันตัวตนอยู่ในที่เดียว แทนที่จะทำซ้ำในทุกสเปก
รูปแบบการเข้าสู่ระบบแล้วใช้งานนี้มีลักษณะคล้ายกับที่คุณจะเขียนในขั้นตอน การทดสอบการผสานรวม API โดยที่การเรียกหนึ่งครั้งจะป้อนข้อมูลเข้าสู่การเรียกครั้งถัดไป
cy.intercept() สำหรับการจำลอง (stubbing)
cy.request() เรียกใช้ API จริง บางครั้งคุณต้องการทำในทางตรงกันข้าม: ดักจับคำขอที่แอปส่วนหน้าของคุณสร้างขึ้นและส่งคืนการตอบกลับปลอม นั่นคือ cy.intercept()
มีความแตกต่างที่สำคัญอยู่ตรงนี้ cy.intercept() จะดักจับเฉพาะคำขอที่แอปพลิเคชันส่วนหน้าของคุณสร้างขึ้นในเบราว์เซอร์เท่านั้น มันไม่ดักจับ cy.request() เพราะ cy.request() ข้ามเบราว์เซอร์ทั้งหมด ใช้ cy.intercept() เมื่อคุณกำลังทดสอบว่า UI ของคุณตอบสนองต่อการตอบกลับ API ที่กำหนดอย่างไร ไม่ใช่เมื่อคุณกำลังทดสอบ API เอง
คุณสามารถสอดแนมคำขอ จำลองด้วยการตอบกลับแบบคงที่ หรือรอคำขอได้ ในการจำลอง ให้ส่งออบเจกต์การตอบกลับ:
cy.intercept('GET', '/api/users', {
statusCode: 200,
body: [{ id: 1, name: 'Ada' }]
})
ตอนนี้คำขอใดๆ จากเบราว์เซอร์ไปยัง /api/users จะส่งคืนเพย์โหลดที่กำหนดไว้ สิ่งนี้ช่วยให้คุณสามารถทดสอบกรณีพิเศษที่ยากจะจำลองกับแบ็คเอนด์จริง เช่น รายการว่างเปล่า ข้อผิดพลาด 500 หรือการตอบสนองที่ช้า
ในการยืนยันว่าคำขอเกิดขึ้น ให้ตั้งชื่อแทนด้วย .as() และรอ:
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')
})
บรรทัด cy.wait('@getUsers') จะหยุดการทดสอบชั่วคราวจนกว่าคำขอที่ถูกดักจับจะได้รับการแก้ไข จากนั้นจะส่งคำขอและการตอบกลับให้คุณยืนยัน ตั้งค่า intercept ก่อนการดำเนินการที่กระตุ้นคำขอ มิฉะนั้นจะตรวจไม่พบสิ่งใด หากคุณกำลังพิจารณาว่าจะจำลองการตอบกลับเมื่อใดเทียบกับการจำลองบริการทั้งหมด โพสต์ของเราเกี่ยวกับ การจำลอง API (API mocking) และ การจำลอง API แบบ Stubbing เทียบกับ Mocking จะอธิบายข้อดีข้อเสีย
เมื่อใดที่ Cypress สำหรับ API มีประโยชน์ และเมื่อใดที่ไม่
Cypress เหมาะอย่างยิ่งสำหรับการตรวจสอบ API ที่อยู่ภายในชุดทดสอบ UI หากคุณกำลังเขียนการทดสอบ e2e อยู่แล้ว และต้องการใส่ข้อมูล (seed data), ตรวจสอบปลายทางที่ UI ของคุณต้องพึ่งพา หรือจำลองการตอบกลับเพื่อทดสอบการจัดการข้อผิดพลาด cy.request() และ cy.intercept() จะช่วยให้ทุกอย่างอยู่ในที่เดียว ไม่ต้องสลับบริบท ไม่ต้องใช้เครื่องมือที่สอง
นอกจากนี้ยังใช้ได้ดีสำหรับการทดสอบ smoke test API แบบสแตนด์อโลนจำนวนหนึ่ง เมื่อ Cypress เป็นตัวรันการทดสอบเพียงตัวเดียวของคุณ และคุณไม่ต้องการเพิ่มการพึ่งพาอื่นๆ
แต่จะเริ่มติดขัดเมื่อเป็นชุดทดสอบ API ขนาดใหญ่แบบสแตนด์อโลน Cypress ถูกสร้างขึ้นมาสำหรับเบราว์เซอร์ ดังนั้นการทดสอบ API จึงเป็นความสามารถที่ซ้อนทับอยู่ด้านบน ไม่ใช่การออกแบบหลัก มีช่องว่างบางประการปรากฏขึ้นเมื่อชุดการทดสอบเติบโตขึ้น:
- ไม่มีเครื่องมือสร้างคำขอแบบภาพ (visual request builder) คำขอทุกคำขอเป็นโค้ด การสร้างส่วนหัว บอดี้ และพารามิเตอร์คำค้นด้วยตนเองจะช้าลงเมื่อคุณมีปลายทางนับร้อย
- ประสิทธิภาพด้อยลงสำหรับไปป์ไลน์ API ที่เป็น CI-only Cypress จะสร้างบริบทเบราว์เซอร์ขึ้นมาแม้สำหรับการรัน API โดยเฉพาะ ซึ่งเป็นภาระที่คุณไม่จำเป็นต้องมี
- ไม่มีการกำหนด API ร่วมกัน รูปร่างของคำขอจะอยู่ในไฟล์ทดสอบของคุณ ไม่ใช่ในสเปกที่ทีมของคุณสามารถนำกลับมาใช้ใหม่ในการออกแบบ เอกสาร และการทดสอบ
นี่คือจุดที่เครื่องมือที่รองรับ API โดยเฉพาะจะเหมาะสมกว่า Apidog ถูกสร้างขึ้นมาโดยมี API เป็นแกนหลัก: คุณออกแบบปลายทางเพียงครั้งเดียว จากนั้นสร้างสถานการณ์การทดสอบกับมันด้วยเครื่องมือสร้างคำขอแบบภาพและการยืนยันด้วยภาพ โดยไม่จำเป็นต้องเขียนโค้ดสำหรับกรณีทั่วไป คำขอ สภาพแวดล้อม และการยืนยันตัวตนจะอยู่ในพื้นที่ทำงานร่วมกัน เพื่อให้ทีมของคุณไม่ต้องสร้างสิ่งเหล่านี้ใหม่จากไฟล์ทดสอบ
สำหรับ CI, Apidog CLI จะรันสถานการณ์การทดสอบที่คุณบันทึกไว้ในโหมด headless ในขั้นตอนใดๆ ที่สามารถรัน Node ได้:
npm install -g apidog-cli
apidog run \
--access-token "$APIDOG_ACCESS_TOKEN" \
-t <scenarioOrSuiteId> \
-e <environmentId> \
-r cli,html,junit
แฟล็ก -t ใช้สำหรับกำหนดเป้าหมายสถานการณ์หรือชุดการทดสอบที่บันทึกไว้ แฟล็ก -e เลือกสภาพแวดล้อม และแฟล็ก -r เลือกรูปแบบรายงาน (cli, html, json, junit) ผลลัพธ์ JUnit สามารถเชื่อมต่อกับแดชบอร์ด CI ส่วนใหญ่ได้โดยตรง เพิ่ม -d หรือ --iteration-data เพื่อขับเคลื่อนการทดสอบจากไฟล์ข้อมูล และ --upload-report เพื่อส่งผลลัพธ์กลับไปยังพื้นที่ทำงานของคุณ CLI จะรันสถานการณ์ที่บันทึกไว้ ไม่ใช่เครื่องมือส่งคำขอแบบโต้ตอบ
วิธีคิดที่เป็นประโยชน์คือ: ใช้ cy.request() สำหรับการตรวจสอบ API ที่สนับสนุนการทดสอบเบราว์เซอร์ของคุณ และใช้เครื่องมือที่รองรับ API โดยเฉพาะเมื่อการทดสอบ API เป็นงานหลัก หากคุณกำลังเปรียบเทียบตัวเลือก โพสต์สรุปของเราเกี่ยวกับ เครื่องมืออัตโนมัติสำหรับการทดสอบ API ที่ทำงานใน CI/CD และ 30 เครื่องมือทดสอบ API ที่ดีที่สุด จะครอบคลุมในด้านนี้
คำถามที่พบบ่อย
`cy.request()` รันในเบราว์เซอร์หรือไม่?
ไม่ cy.request() ทำงานจากกระบวนการ Cypress Node ภายนอกเบราว์เซอร์ นั่นคือเหตุผลที่มันไม่ถูกบล็อกโดย CORS หรือนโยบาย same-origin และเป็นเหตุผลว่าทำไม cy.intercept() จึงไม่สามารถดักจับมันได้ หากคุณต้องการคำขอที่เบราว์เซอร์สร้างขึ้น ให้กระตุ้นผ่านแอปของคุณและดักจับด้วย cy.intercept()
ฉันจะทดสอบการตอบสนอง 400 หรือ 404 โดยไม่ทำให้การทดสอบล้มเหลวได้อย่างไร?
โดยค่าเริ่มต้น cy.request() จะล้มเหลวหากสถานะไม่ใช่ 2xx หรือ 3xx ตั้งค่า failOnStatusCode: false ในออบเจกต์ตัวเลือก จากนั้นยืนยันสถานะด้วยตัวคุณเองด้วย expect(response.status).to.eq(404)
ฉันสามารถใช้ `cy.request()` เพื่อเข้าสู่ระบบก่อนการทดสอบ UI ได้หรือไม่?
ใช่ และเป็นรูปแบบที่พบบ่อย ส่ง POST ไปยังปลายทางการเข้าสู่ระบบของคุณด้วย cy.request() อ่านโทเค็นจาก response.body และเก็บไว้ (ใน Cypress.env(), localStorage หรือ cookie) เพื่อให้การทดสอบ UI ของคุณเริ่มต้นโดยมีการยืนยันตัวตนแล้ว ซึ่งจะช่วยข้ามแบบฟอร์มการเข้าสู่ระบบและเร่งความเร็วของชุดการทดสอบ
ความแตกต่างระหว่าง `cy.request()` และ `cy.intercept()` คืออะไร?
cy.request() ส่งคำขอ HTTP จริงและส่งคืนการตอบกลับจริง ดังนั้นคุณจึงใช้มันเพื่อทดสอบ API cy.intercept() ตรวจสอบหรือจำลองคำขอที่ส่วนหน้าของคุณสร้างขึ้นในเบราว์เซอร์ ดังนั้นคุณจึงใช้มันเพื่อควบคุมสิ่งที่ UI ของคุณได้รับ หนึ่งทำการเรียกเครือข่าย อีกหนึ่งกำหนดรูปแบบมัน
ฉันควรใช้ Cypress หรือเครื่องมือเฉพาะสำหรับการทดสอบ API?
ใช้ Cypress เมื่อการตรวจสอบ API สนับสนุนชุดทดสอบเบราว์เซอร์ที่คุณใช้งานอยู่แล้ว สำหรับชุด API แบบสแตนด์อโลนขนาดใหญ่ ไปป์ไลน์ API ที่เป็น CI-only หรือการกำหนด API ร่วมกันที่ทั้งทีมของคุณใช้งาน เครื่องมือที่รองรับ API โดยเฉพาะอย่าง Apidog จะเหมาะสมกว่า โปรดดู แนวทางปฏิบัติที่ดีที่สุดในการทดสอบ API ของเราสำหรับวิธีการตัดสินใจ
