แนวการทดสอบ React components จาก React Testing Recipes: chapter 2
มาต่อกันจาก แนวการทดสอบ React components จาก React Testing Recipes: chapter 1 เนื้อหาจาก Testing Recipes
จากความเดิมตอนที่แล้ว
เราได้เห็นกระบวนการสร้างชุดทดสอบเบื้องต้นไปแล้วนั่นคือ Setup/Teardown, act() และ Rendering
recap สั้น ๆ คือ
- Setup/Teardown เป็นส่วนของการตระเตรียมของเพื่อใช้สำหรับ test ซึ่งอาจมีของบางอย่างที่แต่ละ test ใช้ร่วมกัน (dependencies) ณ ส่วนนี้เราจะทำให้ depencies ที่ใช้สำหรับแต่ละ test นั้นมีอิสระออกจากกัน เพื่อไม่ให้ test ตัวใดตัวนึงส่งผลกระทบไปยังชุด test ตัวอื่น ๆ (isolated)
- act() เป็น helper function ของตัว react เองที่ช่วยให้มั่นใจได้ว่า React component จะถูกเปลี่ยนแปลงก่อนที่จะนำผลลัพธ์ไปตรวจสอบ (assertion)
- Rendering เป็นวิธีการบอกให้ React component นำผลลัพธ์ไปแสดงผลที่ DOM Element เพื่อให้นำไปเช็คผลลัพธ์ว่าได้ตามที่คาดหวังไว้ไหม
เข้าเรื่อง
มาต่อกันที่เหลือจะเห็นว่าโลกของการเขียน frontend ไม่จบแค่ ui ธรรมดา ๆ ใช่ไหม? การเขียน frontend เราก็จะมี dependencies ที่เราควบคุมไม่ได้อยู่ เช่น การดึงข้อมูลจาก API, การดึงเวลาปัจจุบัน ที่ทำให้การ test frontend ทำได้ยาก แล้วเราจะทำยังไงกันละ ไปดูกัน…
Data Fetching
ในการทดสอบ react component ที่มีการผูก API เราจะจำลองโดยใช้ข้อมูลเสมือนกับยิง API จริง ๆ เพราะ API ของจริงอาจยังไม่พร้อมให้เรียกใช้ก็ส่งผลให้ test fail หรืออาจมีการประมวลผลที่ใช้เวลานาน
note: ขั้นตอนนี้เราสามารถไปเขียน test ที่ระดับ end-to-end ก็ได้โดยใช้เครื่องอย่างเช่น Cypress, Puppeteer
ตัวอย่างโค้ดที่เรียกข้อมูลจาก API
มาเขียน test ได้แบบนี้
จาก test ด้านบนจะเห็นว่าบรรทัดที่ 23–32 จะเป็นการเตรียม fake data แล้วส่ง ผลลัพธ์กลับคืนไปผ่านฟังก์ชัน jest.spyOn(global, “fetch”).mockImplementation(...)
และอีกส่วนที่ทำคือบรรทัดที่ 44 global.fetch.mockRstore()
เป็นคำสั่งเพื่อปล่อยค่าที่ mock ให้ทำงานเหมือนเดิม ไม่ให้ส่งผลกระทบต่อ test อื่น ๆ
Mocking Modules
อาจมีบาง module ที่ทำงานได้ไม่ดีเท่าไรใน testing environment หรืออาจจำเป็นที่จะต้อง test ก็ได้ การจำลอง module ขึ้นมาแทนที่ (dummy) จะช่วยให้การเขียน test ทำได้ง่ายมากขึ้นด้วย
ตัวอย่าง: Contact
component ที่ข้างในมี third-party component คือ GoogleMap
เนื่องจากเป็น third-party component เราคาดหวังว่ามันสามารถทำงานได้ดีอยู่แล้วเลยตัดสินใจที่จะไม่ test ส่วนนี้ เราเลยเปลี่ยนให้กลายเป็น dummy component แบบ 👇
จาก test ด้านบนการทำให้ module ที่เราไม่ต้องการ test กลายเป็น dummy จะใช้คำสั่ง jest.mock('./map', () => {})
ตามบรรทัดที่ 10–18 โดย parameter ที่ส่งเข้าไปคือ path ของ module นั้น ๆ และฟังก์ชันที่ return ค่าของ mock component/data
Events
Events คือ การที่เกิด action บางอย่าง ที่อาจจะเกิดจากผู้ใช้สั่ง เช่น คลิกปุ่ม ส่งแบบฟอร์ม
จากคู่มือของเอกสารเขาแนะนำว่าให้ dispatching real DOM events ไปยัง DOM elements เลย แล้วจึงทำการเปรียบเทียบผลลัพธ์ที่คาดหวัง (asserting)
ตัวอย่าง: Toggle
component
เอามาเขียน test การ โดยมี action การกดปุ่มเป็น 👇
เรื่อง DOM events จะมีอธิบายที่ MDN โดยจากตัวอย่าง test ด้านบนจะมีการใช้งาน MouseEvent เสมือนกับผู้ใช้กดปุ่มใช้งานที่บรรทัด 34 และ 42 ที่สำคัญคือการใส่ { bubbles: true }
เพื่อให้ React listener รับรู้ว่ามี event เกิดขึ้น
note: ถ้าใครใช้ React Testing Library จะมีตัวช่วยในการเรียก event อยู่ซึ่งได้ผลลัพธ์เหมือนกัน
Timers
โค้ดของเราอาจใช้งาน function ที่เกี่ยวกับเวลา เช่น setTimeout
ที่เป็น function ตั้งเวลาการทำงานในอนาคต
ในตัวอย่างจะเป็นการให้ผู้ใช้เลือกตัวเลือกที่จะหมดเวลาถ้าไม่ได้เลือกอะไรภายใน 5 วินาที
เราสามารถเขียน test ได้โดยนำเอา Jest’s timer mocks แล้วจะเขียน test ออกมาได้แบบนี้
จากโค้ดด้านบน เราจะต้องเรียกใช้งาน jest.useFakeTimers()
ที่ปรากฎในบรรทัดที่ 14 และใน test case ก็ใช้คำสั่ง jest.advanceTimersByTime(milliSeconds)
ที่ปรากฎในบรรทัดที่ 33, 39, 50, 59 เป็นการจำลองว่าเวลาได้ผ่านไปกี่ milli seconds (1000 milli seconds มีค่าเท่ากับ 1 second) ทำให้โปรแกรมไม่จำเป็นต้องถึง 5 วินาทีจริง ๆ สุดท้ายคือการเรียกใช้ jest.useRealTimers()
ที่เห็นบรรทัดที่ 22 ตอนที่รัน test เสร็จ
แล้วเจอกัน chapter ต่อไปครับ 😀
Special Thanks
ขอขอบคุณ “สำนักงานส่งเสริมเศรษฐกิจดิจิทัล (depa)” และคณาจารย์ “คณะเทคโนโลยีสารสนเทศ มจธ. (SIT)” ที่ให้การสนับสนุน “ทุนเพชรพระจอมเกล้าเพื่อพัฒนาเทคโนโลยีและนวัตกรรมดิจิทัล (KMUTT-depa)” ซึ่งเป็นทุนที่มอบความรู้ ทักษะและโอกาสดีในการฝึกฝนพัฒนาทักษะที่มีอยู่ให้เฉียบคมมากยิ่งขึ้นครับ ❤