how-to-mock-json-marshal-in-unit-test preview

Go Testing Part 1 - json.Unmarshal mock ยังไงอะ

สำหรับการทำงานกับข้อมูลที่เป็น JSON ในภาษา Go แนวทางที่นิยมที่สุดก็คือการใช้ built-in package ที่ชื่อ json ในการ Encode / Decode ซึ่งโดยทั่วไปแล้วมักจะทำผ่านฟังก์ชั่น json.Marshal และ json.Unmarshal นั่นเอง

พอพัฒนาไปได้สักระยะ เมื่อถึงขั้นตอนทำ Unit Testing จะเจอปัญหาว่า เออเราจะทำยังไงให้ json.Unmarshal มัน error ได้นะ

หากโปรแกรมส่วนนั้นเราสามารถควบคุมข้อมูล []byte ที่จะถูกผ่านเข้าไปใน Unmarshal ได้ ก็ไม่น่าจะมีปัญหา แต่ในกรณีที่เราคุมไม่ได้ ทำอย่างไร

ตัวอย่าง

  // client.go
  package client

  import (
    "json"
    "log"
  )

  func DoSomeWork() (*someType, error) {
    // do something great
    // then you got `dataBytes`
    v := new(someType)
    if err := json.Unmarshal(dataBytes, v); err != nil { // how to make this error?
      log.Printf("unmarshal json error : %s", err.Error())
      return nil, err
    }
    return v, nil
  }

จากโค้ดข้างต้น หากเราสามารถบังคับ dataBytes ให้ออกค่าที่เราต้องการ เราก็จะสามารถทำให้ json.Unmarshal มัน error ได้

แต่ถ้าเราทำไม่ได้ วิธีง่ายที่สุดคือ สร้างตัวแปรมารับฟังก์ชั่น json.Unmarshal ในระดับ package ลองดูตัวอย่าง

  // client.go
  package client

  import (
    "json"
    "log"
  )

  var jsonUnmarshal = json.Unmarshal

  func DoSomeWork() (*someType, error) {
    // do something great
    // then you got `dataBytes`
    v := new(someType)
    // use our self-declared function instead from json package
    if err := jsonUnmarshal(dataBytes, v); err != nil {
      log.Printf("unmarshal json error : %s", err.Error())
      return nil, err
    }
    return v, nil
  }

จากนั้น เราก็จะสามารถ re-assign implementation ให้ฟังก์ชั่น jsonUnmarshal ได้ในตอน test แล้ว

// client_test.go
package client
import (
  "errors"
)

func Test_DoSomeWork(t *testing.T) {
  // re-assign implementation
  jsonUnmarshal = func(bytes []byte, v interface{}) error {
    return errors.New("unmarshal error")
  }
  _, err := DoSomeWork() // ทำงานอะไรบางอย่างเสร็จ จะเออเร่อจากการ unmarshal
  assert.NotNil(t, err)
  assert.Equal(t, "unmarshal error", err.Error())

  // test เสร็จ อย่าลืม assign กลับ
  // เผื่อใน case ถัดไปมีการใช้งานที่ต้องทำงานปกติอยู่
  jsonUnmarshal = json.Unmarshal
}

เสร็จแล้วครับ ง่ายมั้ย จริงๆสามารถใช้กับ utility functions อื่นๆ ได้เช่น time.Now(), io.ReadAll ต่างๆ

ที่ถ้าอยากให้ code coverage 100% ที่ต้องไป cover ตรงส่วน error handling ด้วย ท่านี้ก็จะสะดวกขึ้นเยอะเลย

อันนี้ขอลงไว้เป็น Part แรกละกัน เพราะจริงๆ การ mock ของสำหรับ test ในภาษา Go มันทำได้หลายท่ามากๆ

อันนี้ถือเป็นท่าที่ simple ไปก่อนละกันเนอะ ไว้มีเวลาจะมาเขียนอัพเดทว่า จะเขียนแบบอื่นได้ยังไง

← Front Matter - Markdown Articles Management Tool การเลือกใช้ฟอนต์สำหรับหน้าเว็บง่ายๆ แบบไม่คิดเยอะ →