fbpx
Blogs 23 November 2021

เริ่มต้นเขียน Unit Test บน Nest.js อย่างไร ให้มั่นใจว่าโปรแกรมของเรามีคุณภาพ

ตามคำนิยามแล้ว Unit Testing คือ “วิธีการทดสอบ Software ที่ใช้ทดสอบส่วนที่เล็กที่สุดของ Code เพื่อแสดงให้เห็นว่ามันควรจะทำงานได้อย่างที่ควรจะเป็น โดยโปรแกรมเมอร์” ซึ่งใน Nest.js นี้ เราจะทำการทดสอบในส่วน Method ของแต่ละ Class โดยใช้ framework ที่ชื่อว่า Jest

หลักการเขียน Unit Testing คือ

  1. เทสต์เคสที่เขียนต้องไม่ต่อกับ Database จริง
  2. เทสต์เคสที่เขียนต้องไม่เรียกใช้ Service ภายนอก

ด้วยเหตุนี้เราจึงจะใช้การ Mock หรือการจำลองผลลัพธ์ของการเรียกใช้ Database และ Service ขึ้นมา เพื่อให้ Method ที่เรากำลังเขียนเทสใช้ค่าพวกนั้นแทนการเรียกใช้ Database และ Service ภายนอก

การเขียน Unit test สำหรับ function ทั่วไป

ตัวอย่าง method ที่จะเขียนเทส

หลักการของ Method createUser นี้คือ การตรวจเช็คก่อนว่าใน Database ของเรามี user ที่จะสร้างนี้อยู่แล้วหรือไม่ โดยส่ง name ของ userInfo เข้าไปใน method findOne ถ้ามีอยู่แล้วจะทำการ return ข้อมูล user ของคนนั้นออกมาให้ แต่ถ้าไม่มี จะทำการ save ข้อมูลของ userInfo นี้ลงไปใน Database

ตัวอย่างของการเขียน Mock Entity แบบง่ายๆ

โดยตัวอย่างข้างต้นเป็นการจำลองฟังก์ชั่น findOne, save

ตัวอย่างการเขียน Unit Testing ของ Method ‘createUser’

ในส่วนแรกของตัวอย่าง จะเป็นการ Configuration สำหรับ Unit Testing  โดย beforeEach (คือ method ที่ไว้กำหนดสิ่งที่จะต้องทำ ก่อนที่จะไปทำแต่ละ test case) จะกำหนดค่าต่างๆ ไว้ ตัวอย่างเช่น

ซึ่งเป็นการกำหนด mock class ของ Entity ของเรา โดยใช้ class mockUsersEntity แทน

ต่อไปคือส่วนที่เป็นการเขียน unit testing

จากตัวอย่างของ method createUser จะเห็นว่าเราต้องส่ง parameter เข้าไป 1 ตัว คือ userInfo  เราจึงจำลองค่า mockUser และค่าต่างๆที่จำเป็นขึ้น

เมื่อจำลองค่าขึ้นมาเรียบร้อย เราก็จะมาจำลองการการเรียกใช้งาน mock Entity ของเราในแต่ละ method

การจำลองค่าที่ส่งคืนกลับมา จะใช้ฟังก์ชั่น mockResolvedValue() และกำหนดส่งค่าที่ต้องการให้ส่งกลับมาเข้าไป โดยเราจำลองว่า ถ้าเรียก findOne ให้คืนค่ามาเป็น undefined เพื่อจำลองว่า ยังไม่มี user ที่ชื่อ June อยู่ใน Database เมื่อเรียก save ก็ให้คืนค่าเป็น mockUser ที่เรากำหนดไว้

ส่วนบรรทัดนี้

เป็นการเรียกใช้ createUser ของเราแล้วนำผลลัพธ์ที่ได้มาเก็บไว้ในตัวแปรที่ชื่อว่า resultTestCreateUser

จากนั้นเราจะตรวจสอบว่า ค่าที่ได้จากการทดสอบตรงกับค่าที่เรากำหนดหรือไม่

โดยใช้ expect().toStrictEqual() เพื่อเปรียบเทียบว่า 2 ตัวแปรนี้มีค่าต่างๆ เหมือนกันหรือไม่ ถ้าเหมือนกันจะผ่านไปทำฟังก์ชั่นถัดไป ถ้าไม่เหมือนกัน จะแจ้ง error ว่าทั้ง 2 ตัวแปรนี้แตกต่างกันอย่างไรบ้าง

ในส่วนของ .toBeCalledWith() นั้น เป็นการตรวจสอบพฤติกรรมภายใน method ว่าเจ้าฟังก์ชั่น mockUsersEntity.findOne ที่เราสร้างไว้เนี่ย มันถูกเรียกด้วย parameter { where: { name: mockUserInfo.name }} หรือไม่

และ mockUsersEntity.save ถูกเรียกด้วย parameter  userInfo เหมือนที่เราได้ส่ง parameter เข้าไปใน userService.createUser หรือไม่

การใช้ spyOn() เพื่อ mock function

ในกรณีที่ method ที่เราทำการเทสมีการเรียกใช้ method อื่นใน class เดียวกัน เราสามารถใช้ jest.spyOn() เพื่อทำการจำลองค่าที่จะคืนกลับมาได้ เช่น

จะเป็นการจำลองว่า ถ้าเรียกใช้ method create ใน class userService จะคืนค่าเป็น result ตามที่เรากำหนดไว้

การตรวจสอบ Exception

ในกรณีที่ต้องการที่จะทดสอบการ throw exception จากเดิม เราสามารถเปลี่ยนวิธีการเขียนจากรูปแบบเดิม เช่น

เป็น

โดยเมื่อ method createUser มีการ throw exception ออกมา จะเข้าตรง catch จากนั้นเราจะตรวจสอบว่า exception ที่ throw ออกมานั้นเป็นตามที่เรากำหนดไหม โดยในตัวอย่างจะต้องเป็น NotFoundException

จากนั้นทำการสร้างตัวแปร exception ขึ้นมา เพื่อนำมาตรวจว่า error message เป็นเหมือน expectedResult ที่เรากำหนดไว้หรือไม่

การ Mock Library

ในกรณีที่เราต้องเขียน unit test ของ method ที่มีการใช้ library ที่ import เข้ามา เราต้อง mock library ขึ้นมา เนื่องจากเราไม่สามารถ mock class ในนั้นได้

ตัวอย่าง method ที่ใช้ library “google-auth-library”

ตัวอย่างการ mock library ของ google-auth-library

จากตัวอย่างการ mock library ของ google-auth-library จะเห็นว่าเรา return เป็น object ที่มี OAuth2Client อยู่ข้างใน โดย OAuth2Client นี้จะใช้ mockImplementation() เพื่อ mock function ให้รองรับการใช้แบบ class ในบรรทัดนี้

ถ้าหากไม่ได้ใช้งานแบบ class เราสามารถเปลี่ยนไปใช้ mockResolvedValue หรือ mockReturnValue แทนได้เลย

หลังจากนั้น เรากำหนดให้ OAuth2Client มีการคืนค่าเป็น object ที่มีฟังก์ชั่น verifyIdToken เนื่องจากในโค้ดเราเรียกใช้ verifyIdToken ของ OAuth2Client ดังนี้

โดยเราจะใช้ mockResolvedValue กับ verifyIdToken เพราะมันเป็น asynchronous function และให้คืนค่าเป็น object ที่มีฟังก์ชั่น getPayload ข้างใน ตามการใช้งานของโค้ดบรรทัดนี้

โดยเราจะใช้ mockReturnValue กับ getPayload เพราะมันเป็น synchronous function เพื่อกำหนดค่าที่จะคืนกลับมาเมื่อเรียก getPayload

ตัวอย่างการเขียน unit test ของ method getGoogleProfile

กรณีที่เราต้องการตรวจสอบค่าที่ส่งเข้าไปใน library ที่เรา mock ขึ้นมา เราสามารถเปลี่ยนวิธีการเขียนจาก

เป็น

ทั้งนี้ยังมีข้อจำกัดของการ mock library ที่ควรระลึกถึง ดังนี้

  1. เราต้องใส่การ mock library ไว้ใต้ import statement ในไฟล์ test ของเรา
  2. ค่าที่จะ mock หรือตรวจสอบสามารถกำหนดได้เพียงแค่ครั้งเดียวและไม่สามารถเปลี่ยนแปลงค่าในแต่ละ test case ได้