การทดสอบ application ในบางครั้งก็จะมี dependencies อื่น ๆ ที่เกี่ยวข้องมากมาย โดยหนึ่งในนี้คือ การเรียก API data ที่ไม่สามารถทราบได้เลยว่าการที่ API ตอบกลับมานั้นจะช้าหรือเร็วแค่ใหน การเทสบางครั้งอาจราบลื่นเป็นปกติ เพราะไม่มีการถูกขัดจังหวะด้วย response time ของ API data ที่เร็ว แต่เมื่อ run test อีกครั้งกับได้ผลรับไม่เหมือนเดิม เกิด failures เพราะมี error message แจ้งกลับมาว่า timeout waiting 5000ms บ่งบอกได้ถึงการรอ network request API ที่นานจนโปรแกรมรอไม่ไหว แบบนี้จะแก้ได้อย่างไร
การ Mock API แก้ pain point นี้ได้
การ Mock API ช่วยแก้ pain point นี้ได้ โดยมีหลักการคือการ จำลอง response data ที่สร้างขึ้นมาเพื่อที่จะไม่ต้องเรียก API request จริง วิธีการนี้จะช่วย ควบคุม response time ได้ทำให้ test ของเราทำงานได้ราบลื่นไม่ติดขัด
Network Requests Route คืออะไร
Cypress ได้สร้าง API Command ที่ชื่อว่า route เพื่อใช้บริหารจัดการ XHR Object Request ของระดับ HTTP โดยทำการสร้างสภาพแวดล้อมเสมือนคอยตรวจสอบดักจับการเรียก Network URL ที่สร้างขึ้นโดยที่เราสามารถปรับปรุงเปลี่ยนแปลง Request ,Response ของ HTTP ในระดับ network layer ได้
ตัวอย่างและวิธีการเรียกใช้งาน
cy.server() cy.route(url) cy.route(url, response) cy.route(method, url) cy.route(method, url, response) cy.route(callbackFn) cy.route(options)
usercase ที่จะนำมายกตัวอย่างคือการ call API ของ coronavirus-19-api data ของประเทศทั้งหมดในโลกที่มีการติดเชื้อ Covid ณ ปัจจุบัน จะยกตัวอย่างการเปลี่ยนแปลงข้อมูล เมื่อเรียก api นี้ route จะทำหน้าที่ mock response ของ API เดิมที่จะ return Response Data ของทุกประเทศ แต่จะ return Response Data ของ Thailand เท่านั้น
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Window Methods</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css" /> </head> <body> <div class="ui container"> <h2>Course Cypress Automated Testing</h2> <div class="ui top attached tabular menu"> <a class="item" href="index.html" data-tab="zero">Index</a> <a class="item" href="register.html" data-tab="first">Register</a> <a class="item" href="register-list.html" data-tab="first-list" >Register List</a > <a class="item" href="window.html" data-tab="second">Window</a> <a class="item" href="login.html" data-tab="third">Login</a> <a class="item" href="change-password.html" data-tab="four" >Change Password</a > <a class="item active" href="table.html" data-tab="five" >Table Coronavirus-19</a > <a class="item" href="elements.html" data-tab="six">Chai-jQuery</a> </div> <div class="ui bottom attached tab segment active" data-tab="first"> <table class="ui tablet stackable celled striped table" id="covidState"> <thead> <tr> <th>No.</th> <th>Country</th> <th>Cases</th> <th>Today Cases</th> <th>Deaths</th> <th>Today Deaths</th> <th>Recovered</th> <th>Active</th> <th>Critical</th> </tr> </thead> <tbody></tbody> </table> </div> </div> <script> Number.prototype.toCurrency = function (n = 2, x = 3) { var re = '\\d(?=(\\d{' + (x || 3) + '})+' + (n > 0 ? '\\.' : '$') + ')'; return this.toFixed(Math.max(0, ~~n)).replace( new RegExp(re, 'g'), '$&,' ); }; window.onload = () => { const data = fetch('https://coronavirus-19-api.herokuapp.com/countries') .then((http) => http.json()) .then((data) => { //console.log('data ::==', data); const $covidState = document.getElementById('covidState'); let $tbody = covidState.children[1]; // tbody //console.log('$tbody ::==',$tbody) data .map((item, index) => { return { ...item, ...{ no: index + 1 } }; }) .forEach((item) => { let $tr = document.createElement('TR'); $tr.innerHTML = ` <td>${item.no}</td> <td>${item.country}</td> <td>${(item.cases || 0).toCurrency(0)}</td> <td>${(item.todayCases || 0).toCurrency(0)}</td> <td>${(item.deaths || 0).toCurrency(0)}</td> <td>${(item.todayDeaths || 0).toCurrency(0)}</td> <td>${(item.recovered || 0).toCurrency(0)}</td> <td>${(item.active || 0).toCurrency(0)}</td> <td>${(item.critical || 0).toCurrency(0)}</td> `; $tbody.appendChild($tr); }); }); }; </script> </body> </html>
การทำงานของโปรแกรมมหน้านี้ เมื่อโหลดหน้าขึ้นมาจะไปเรียก API Covid นี้ทันทีด้วย window.fetch
ทดสอบสร้าง testscript ของการใช้คำสั่ง route
describe('ทดสอบการทำงานและเรียกใช้งาน Route', () => { let polyfill before(() => { const polyfillUrl = 'https://unpkg.com/[email protected]/dist/fetch.umd.js' cy.request(polyfillUrl).then(response => { polyfill = response.body }) }) Cypress.on('window:before:load', win => { delete win.fetch win.eval(polyfill) }) it('เรียก API Covid', () => { cy.server() cy.route({ url: '**/countries', method: 'GET', // Route all GET requests response: [{ "country": "Thailand", "cases": 3202, "todayCases": 5, "deaths": 58, "todayDeaths": 0, "recovered": 3085, "active": 59, "critical": 1, "casesPerOneMillion": 46, "deathsPerOneMillion": 0, "totalTests": 603657, "testsPerOneMillion": 8648 }] }).as('routeCovid') cy.visit('https://cypress-testing-143fd.web.app/table.html') .get('#covidState').contains('Country') .wait('@routeCovid', { timeout: 10000 }).its('status').should('have.eq', 200) }) })
อธิบายการทำงานในส่วนโค๊ดนี้
บรรทัดที่ 18 เริ่มสร้าง network server (cy.server())
บรรทัดที่ 19 สร้าง route เพื่อ mock api ที่ติดต่อภายนอกโดยกำหนด response คือ array object ของ ประเทศไทย เท่านั้น
บรรทัดที่ 37 visit เข้าไปที่หน้าเว็บตัวอย่าง
บรรทัดที่ 39 ตรวจสอบ status code equal 200 หรือไม่
เมื่อลองทดสอบ run test การทำงานของโปรแกรมจะเปลี่ยนไป ระบบจะแสดงแค่ข้อมูลของประเทศไทย โดยที่ไม่ได้มีการแก้ไขโปรแกรมที่ทดสอบแต่อย่างไร เพียงแต่ทำการ mockup API ที่แสดงข้อมูลด้วย Cypress Route เท่านั้น
เพียงเท่านี้เราก็สามารถควบคุม data ที่มาจากการ call API จากที่ต่าง ๆ ได้ แล้วโดยไม่ต้องรอการ response กลับจาก API นั้น ๆ
หากเพื่อนคนใดอยากที่จะทำความรู้จัก Route Command นี้เพิ่มเติมให้เข้าไปที่ลิ้งนี้ได้เลย Cypress Route