มาสร้าง Custom Assertion ของตัวเองบน Cypress.io กันเถอะ

Traitanit Huangsri
Cypress.io Thailand
4 min readApr 12, 2020

สวัสดีครับ บทความนี้ผมจะพาทุกคนไปดูวิธีการสร้าง Custom Assertion ของตัวเองบน Cypress.io กันครับ บอกก่อนเลยว่าเมื่ออ่านบทความนี้จบ ทุกคนจะสามารถสร้าง Assertion ของตัวเองเพื่อเอามาใช้ใน Project ของเราได้อย่างสนุกสนานแน่นอนครับ ตามผมมาเลยครับ!

Assertions บน Cypress

ในการเขียนเทสทุกครั้ง การทำ Assertion ถือเป็นสิ่งที่สำคัญที่สุดใน Test Case ของเรา เพราะนั่นคือการนำ Expected Result ที่เราต้องการมาเทียบกับสิ่งที่ระบบหรือ Application ของเรา Return ออกมา (Actual Result) ว่าเป็นไปอย่างที่เราต้องการหรือไม่? เป็นตัวกำหนดว่า Test Case ข้อนั้นๆ จะรันแล้วผ่านหรือพัง! เลยก็ว่าได้ ในทุกครั้งที่เขียน Test Case ใหม่ก็อย่าลืมทำ Assertion กันทุกครั้งนะครับ ไม่งั้นคุณก็เหมือนไม่ได้เทสอะไรเลย ฮ่าฮ่า

ซึ่งบน Cypress ได้มีการเตรียม Assertion Library มาให้เราอยู่แล้ว โดยที่เราไม่ต้องไป install หรือหา library ข้างนอกมาเพิ่มเลย โดย Cypress ได้นำ Chai Assertion Library มาเป็น Assertion library หลัก และยังมี Additional Assertion Library ที่ทาง Cypress นำมาเพิ่มเติมให้อีกอย่าง Sinon-Chai (ใช้สำหรับ Assert Stub Object ต่างๆ) และ Chai-jQuery (ใช้สำหรับ Assert พวก UI Element, Property ต่างๆ บน Web App)

ตัวอย่างการใช้ Assertions บน Cypress

การเรียกใช้ Assertion Library สามารถทำได้หลายวิธี โดยจะใช้สไตล์การเขียนลักษณะที่เราเรียกว่า Chainer นั่นคือเป็นการ call function ต่อเนื่องไปได้เรื่อยๆ ในบรรทัดเดียว ซึ่งเราสามารถเขียน Assertion บน Cypress ได้หลากหลายรูปแบบ ยกตัวอย่างเช่น

1. BDD Assertions

BDD Assertions คือการเขียน Assertion ในสไตล์​ Behavior Driven Development หรือพูดง่ายๆ คือเป็นโค้ดที่อ่านแล้วใกล้เคียงกับภาษาคนมากที่สุด (Human-readable code, ส่วนตัวผมชอบ Pattern นี้ที่สุด) โดยใช้ 2 functions หลักคือ expect และ should นั่นเอง เช่น

expect(name).to.equal('foo')
cy.wrap('foo').should('be.foo')

2. TDD Assertions

สไตล์การเขียน Assertion แบบที่ 2 เราจะเรียกว่า TDD (Test Driven Development) Assertions ซึ่งจะคล้ายๆ กับฟังก์ชัน assert ที่มีมาให้ใช้ใน Node.js อยู่แล้ว แต่ TDD Assertions ของ Chai จะเพิ่มเติมความสามารถเข้าไปอีก เช่น

assert.equal(3, '3', 'this is example of error message')
assert.isNumber('abc', 'abc is not number')

3. Sinon-Chai

Sinon-Chai คือ Assertion Library เพิ่มเติมที่ทาง Cypress นำมาใช้กับการเช็คค่าที่อยู่ในพวก Stub หรือ Spy Object ต่างๆ สำหรับใครที่ยังไม่รู้จักว่า Stub และ Spy บน Cypress คืออะไร ใช้งานยังไงสามารถอ่านบทความนี้ได้เลยครับ

cy.stub().callsFake('fakeFunction).as('stub')
cy.get(@stub).should(‘be.calledOnce’)

สร้าง Custom Assertions ของตัวเองบน Cypress

จริงๆ แล้วด้วย Assertion Library ที่ Cypress เตรียมมาไว้ให้เราใช้งานนั้นผมคิดว่าสามารถใช้เขียนเทสทั่วๆ ไป ได้ 100% อย่างเพียงพอโดยที่ไม่ต้องมีการใช้ Library อื่นๆ หรือเขียน Custom Assertion อะไรเพิ่มเลย แต่ครับแต่! ถ้าคุณอยากจะสร้าง Custom Assertion ของตัวเองขึ้นมา​ (ก็อยากเท่ห์ไม่เหมือนใครอ่ะ จะทำไม?) ก็สามารถทำได้เช่นกันครับ โดย Chai.js นั้นก็เปิดให้เราสามารถสร้าง Custom Assertion ของตัวเองได้ โดยทาง Chai.js ก็ได้เตรียม Utilities และ Helper function ต่างๆ เพื่อให้เราสามารถสร้าง Custom Assertion ของเราให้ง่ายที่สุดด้วยครับ

ตัวอย่างการสร้าง Custom Assertion ของตัวเอง

ในบทความนี้ผมจะขอยกตัวอย่างการสร้าง Custom Assertion ง่ายๆ ซัก 1 Use Case นะครับ นั่นก็คือผมจะทำการสร้าง Assertion เพื่อเช็คว่า Text ที่ Display อยู่ใน Web App ของผมนั่นใช้ Font เป็นตัวหนา (Bold) หรือไม่?

ผมได้ทำการสร้างตัวอย่างเว็บแบบง่ายๆ โดยให้มี Element ที่แสดงผลด้วย Font ตัวหนา (Bold) และตัวปกติ (Normal) โดยใส่ CSS Stylesheet เข้าไป และแน่นอนว่าผมใส่ Custom Data Attribute เพื่อให้การเขียนเทสด้วย Cypress เป็นไปอย่างราบรื่นด้วยครับ (เป็น Practice ที่ Web Developer ทุกคนควรทำด้วยนะครับ)

การเขียนเทสสำหรับเว็บนี้เพื่อเช็คว่า Text ที่เรากำลังสนใจแสดงผลเป็นตัวหนามั้ย จริงๆ แล้วเราไม่ต้องสร้าง Custom Assertion ก็ได้นะครับ เพราะ Chai-jQuery สามารถเช็คเรื่องนี้ได้อยู่แล้วแบบนี้ครับ

รันเทสออกมาก็ผ่านทั้งหมด เช็คได้ตามที่ต้องการ แต่ผมรู้สึกว่าการเช็คว่า have.css, 'font-weight', '700' นี่มันดูอ่านแล้วงงๆ นะครับ คนอื่นมาอ่านอาจจะสงสัยว่า font-weight = ‘700’ คืออะไร ? ผมเลยอยากจะสร้าง Custom Assertion ของตัวเองขึ้นมาทำให้โค้ดของผมอ่านออกมาเป็นภาษามนุษย์มากขึ้นครับ

Step 1: สร้าง Custom Assertion Function ที่ Cypress Support File

ใน Cypress Folder Structure จะมี Folder หนึ่งที่ชื่อว่า Support ซึ่งจะมีไฟล์​ index.js แถมมาให้ โดยไฟล์นี้จะถูกรันทุกครั้งก่อนที่จะเริ่มเทสในทุกๆ Spec File ซึ่งมันจะช่วยให้เราสามารถกำหนดพวก Global Setup ต่างๆ อย่างในกรณีนี้คือเราต้องการสร้าง Custom Assertion ในจุดนี้นี่แหละครับ

โดยวิธีการคือให้เราทำการสร้าง Custom Assertion Function ขึ้นมาโดยรับ Argument 2 ค่า คือ _chai และ utils Object ซึ่งจะถูกส่งผ่านมาโดย Chai Library อีกทีครับ

  • _chai: object คือ instance object ของ Chai ซึ่งจะมีฟังก์ชันที่เราสามารถใช้ทำ assert ได้อยู่ใน object นี้ด้วยครับ
  • utils: คือ object ที่รวมเอา utility methods ของ Chai ซึ่งเราสามารถใช้มันในการช่วยทำพวก Assertion ของเราให้ดีขึ้นได้ครับ เช่นใช้ในการเพิ่ม additional message ออกไปตอนที่ assert ค่าแล้ว failed เป็นต้น อ่านวิธีใช้เพิ่มเติมได้ที่นี่ครับ (หัวข้อ Accessing Utilities)

ผมทำการสร้างฟังก์ชัน assertFontWeightBold ขึ้นมาเพื่อทำการเช็คว่า Element ที่ผมสนใจนั้นมี CSS Property ที่เป็น font-weight ค่าเท่ากับ 700 หรือไม่? โดยผมสามารถ access ค่าของ Web Element ที่มีการ Get ออกมาได้ผ่าน this._object ตัวนี้นะครับ และผมได้ทำการ access css property ที่มี key = font-weight แบบที่เขียนในโค้ดตัวอย่างครับ

Note: เราไม่สามารถใช้ Arrow Function แบบนี้ () => {} ในการสร้าง Custom Assertion ได้นะครับ จะต้องใช้วิธีการประกาศ​ Function แบบดั้งเดิมเท่านั้น

สิ่งที่ผมได้ออกมาจาก parseInt(this._obj[‘css’](‘font-weight’)) ก็คือค่าของ css property font-weight ของ Element นั้น ณ ขณะ Runtime ซึ่งก็คือ Actual data นั่นเอง (เช่น 700) และผมก็ทำการนำค่านี้ไป assert เช็คกับ expected result ของเราได้โดยใช้ฟังก์ชัน this.assert(condition, [messages]) ได้เลยครับ โดย argument แรกคือสิ่งที่เราใช้ตัดสินใจว่า assert passed หรือ failed นะครับ เป็นค่า boolean ที่ถ้า return true แปลว่า passed, false แปลว่า failed ครับ

ส่วน argument ที่ 2 คือ message ที่ใช้ display ตอน assertion failed ในกรณีปกติ ส่วน argument ที่ 3 คือ message ที่ใช้ display ตอน assertion failed ในกรณีที่เป็น negate (คือใส่ .not ตอนเรียกฟังก์ชันมาข้างหน้า)

หลังจากนั้นให้เราทำการ add method ของเพิ่มเข้าไปใน Chai Assertion โดยเรียก _chai.Assertion.addMethod(‘boldText’, assertFontWeightBold) argument แรก boldText คือชื่อฟังก์ชันที่เราจะให้ test เรียกใช้ ส่วน argument ที่สองคือ callback function ที่เรา implement assertion logic เข้าไปครับ

และสุดท้ายให้เราทำการ register custom assertion ของเราเข้าไปโดยใช้คำสั่ง chai.use(customAssertions) customAssertions คือชื่อฟังก์ชันที่เรา implement custom assertion เข้าไปนะครับ จะชื่อ foo หรืออะไรก็ได้แล้วแต่เราเลยครับ

Step 2: สร้าง Code Completion เพื่อให้ Cypress รู้จัก Custom Library ของเรา

Custom Assertion ที่เราสร้างขึ้นมานี้ จะสามารถใช้งานได้ในทุกๆ ไฟล์ที่อยู่ใน Project เดียวกับเราได้เลยครับ แต่เพื่อให้ใช้งานได้ดียิ่งขึ้น เราจะทำให้ Custom Assertion ของเรามัน Auto Complete ได้ด้วย วิธีการก็ง่ายๆ ครับ ให้เราทำการสร้าง Typescript Definition File index.d.ts ขึ้นมาใน Support Folder แล้วเพิ่ม Custom Assertion Function ของเราเข้าไปแบบนี้ครับ (เพิ่มทั้งเคสปกติและ negate ด้วยนะครับ)

และใน Spec File ของเราก็ให้ทำการ Import Type Definition เพิ่มเข้าไปครับ ทีนี้ตอนที่เราเขียน Test ก็จะมี Custom Assertion ของเราโผล่ขึ้นมาให้เลือกด้วย ดีงามสุดๆ ครับ

ทีนี้เราก็เขียนเทสใหม่โดยใช้ Custom Assertion ที่เราสร้างขึ้นมาเองกับมือกันครับ

เมื่อรันเทสออกมา เราจะเห็นว่า Message ดูอ่านรู้เรื่องมากขึ้นว่า Assert อะไรและผลลัพธ์เป็นอย่างไร

ในกรณีที่ Test Failed ก็จะสามารถเห็น Message ที่เราใส่ไว้ใน Custom Assertion ของเราเช่นกันครับ ซึ่งใน Cypress 4.3.0 มีการปรับปรุง Error Message ใหม่ให้ดูเด่นชัดและอ่านง่ายมากยิ่งขึ้นอีกด้วยครับ อย่าลืม Upgrade กันนะครับ

ของแถม

สำหรับใครที่คิดว่าอยากจะเขียน Custom Assertion เพิ่มเติมเพื่อจะนำมาใช้ในโปรเจ็คของตัวเอง ก่อนที่จะลงมือเขียนใหม่เองนั้น จริงๆ แล้ว Chai มีคนสร้าง Plugin ที่ Extend ความสามารถเดิมของ Chai ไปมากมายเลยครับ ซึ่งเราสามารถนำมาใช้กับ Cypress ได้เลยโดยที่เราอาจจะไม่ต้องมาเขียนเองเลยก็ได้ ผมขอยกตัวอย่าง Plugin ที่น่าสนใจดังนี้ครับ

  • chai-string: ใช้ในการ check string ในรูปแบบต่างๆ เช่น equalIgnoreCases, equalIgnoreSpaces เป็นต้น
  • chai-colors: ใช้ในการเช็คค่าสีต่างๆ เช่น should(‘be.colored’, ‘#0000000’)
  • chai-date-string: ใช้ในการ validate ว่า string นั้นเป็น date-string หรือเปล่า เจ๋งดีครับ

วิธีการใช้งาน Plugin เหล่านี้ใน Cypress ก็ง่ายๆ ครับ เพียงแค่เรา install library เหล่านี้แล้ว import เข้าไปใน Cypress Support File ก็สามารถนำไปใช้งานได้ในทุกๆ Spec File เลยครับ ยกตัวอย่างเช่น ถ้าผมต้องการใช้ chai-date-string plugin สามารถทำได้แบบนี้ครับ

$sh yarn add chai-date-string or npm install chai-date-string# in cypress/support/index.js
import chaiDateString from 'chai-date-string'
chai.use(chaiDateString)

สามารถเข้าไปดู Plugins ตัวอื่นๆ ที่อาจจะเหมาะกับโปรเจ็คของเราได้ที่ https://www.chaijs.com/plugins/ ได้เลยครับ (มีเยอะมากๆ)

สรุป

จะเห็นได้ว่าเราสามารถสร้าง Custom Assertion ของตัวเองบน Cypress ได้อย่างไม่ยากเลยใช่มั้ยครับ ผมหวังว่าบทความนี้จะสร้างไอเดียใหม่ๆ ในการเขียนเทสของทุกคนให้ดียิ่งขึ้นและมีประสิทธิภาพมากขึ้นไม่มากก็น้อยนะครับ ถ้าใครมีข้อสงสัยอะไรเพิ่มเติม สามารถเข้าร่วมกลุ่ม Cypress.io Thailand และฝากคำถามไว้ในน้ันได้เลยนะครับ ผมจะพยายามช่วยหาคำตอบมาให้ได้ดีที่สุดนะครับ

--

--