[Golang] หลักการเขียน Unit test และ การ Mockup สำหรับ Http Client พร้อมตัวอย่าง
Intro
โดยปกติแล้ว เวลาเราจะเขียนเทสที่ตัว service เราจะต้องทำการ mock dependency ซึ่งในที่นี้ก็คือ http client ใช่ไหมครับ ซึ่ง วิธีการทั่วไปเราจะทำการ Wrap http client ของเรา เช่น `net/http`, `resty` พอ Wrap เสร็จเราก็จะทำการเขียน Interface เพื่อทำการ mock ทั้ง class เพื่อเรียกใช้ในตอนเทส Service ครับ
แล้วเราจะทำการ เทส Wrap http client ของเรายังไง?
อย่างเช่น ถ้าเราอยากจะรู้ว่า map request / response ดัก Error ถูกต้องรึเปล่า
วันนี้มี Solution มานำเสนอครับ
[Solution 1] Mock External Service
solution นี้เป็นท่าที่ผมใช้มาตลอดคือการ Mock external service แล้วยิงหาโดยการเปลี่ยน Base url ไปเลย
โดยการใช้ APIB (API Blueprint) หรือ Mockoon
จริงๆ แล้วท่านี้เหมาะสำหรับการทำ Integration test มากกว่า ไม่ใช่ Unit test (เพราะถ้า mock ตาม scenario นี้ถึงตายแน่ๆ)
Pros
- เวลาเรา Dev สามารถใช้ตัว Mock API ในการ Dev ได้เลย ไม่จำเป็นต้องรอให้ฝั่งปลายทางทำเสร็จ หรือ แม้ปลายทาง Server down เราก็ทำงานได้ปกติไม่มีปัญหา
- ไม่ต้องลงลึกการ Mock ในแต่ละภาษา จะดีในกรณีที่คุณใช้หลาย Stack แล้วไม่อยากจดจำการ Mock ในแต่ละภาษา
- ไม่ต้องเสียเวลา Mock ตอนเทส และตอน Dev ทำครั้งเดียวจบ ใช้ด้วยกันได้เลย
Cons
- มันช้าครับ เวลาเราจะรันเทส หรือ CI เราต้องรัน service mock ขึ้นมาด้วย
- ใช้เวลาเขียนมากกว่าการ Mock ใน Code เพราะต้องเขียน Mock API ด้วย และตาม scenario อีก
- maintain เหนื่อยใช้ได้ แม้ว่าจะทำที่เดียวแล้วจบใช้ได้ทั้งสอง แต่การ Mock ตาม scenario ขึ้นมาเป็น Mock API ก็ใช้ effort เยอะกว่า mock ใน unit test ครับ
[Solution 2] Mock in Unit test
เราจะทำการเขียนโดย Mock ใน unit test ไปเลย
Pros
- เขียนง่าย ทำงานไว เหมาะกับ Unit test จัดๆ
- ไม่ต้องรัน service mock ทำให้ Setup CI ได้ง่าย
- Maintain ในระดับกลางๆ แม้ว่าจะต้องทำ Mock 2 ส่วนแยกกัน แต่โดยปกติ API Mock เราจะเขียนแค่ 1–2 case เช่น success / error แต่ในขณะที่ตัว Mock Unit test เราจะเขียนครบทุก scenario มี 20 ก็เขียน 20
Cons
- ต้องศึกษา และหา tool ในการทำที่เหมาะกับแต่ละภาษา
- ต้อง mock สองครั้งแยกระหว่าง API Mock สำหรับ Develop กับ Mock สำหรับ Unit test
ภาคปฏิบัติของ [Solution 2] Mock in Unit test
ตัวอย่าง client.go
จะทำการ Mock ได้ดังนี้
บรรทัด 5: ทำการประกาศ clientMock
บรรทัด 8: apply client เข้า `httpmock` (แนะนำใช้ใน Before all)
บรรทัด 11: reset mock (แนะนำใช้ใน Before each)
บรรทัด 13–20: ทำการ mock response และ บันทึกลง httpmock เพื่อรอรับ request
บรรทัด: 22–25: ทำการ call http แบบปกติ
บรรทัด 29: ทำการ remove httpmock ออก (แนะนำใช้ใน After all)