Go 言語(以下 Golang)で、俺様関数内の
json.Marshal
/json.MarshalIndent
を失敗させたい。つまり、JSON 変換に失敗するテスト・ケースが欲しい。
テストしたい関数
type Foo int
func (f Foo) Dump(w io.Writer) (n int, err error) {
if w == nil {
return 0, errors.New("dump failed: the io.Writer w is nil")
}
byteJSON, err := json.Marshal(f)
if err != nil {
// ここのエラーに、たどり着けない
return 0, errors.Wrap(err, "dump failed: can not marshal NodeLog to JSON")
}
return w.Write(byteJSON)
}
- オンラインでユニット・テストをみる | テストをパスできないケース @ Go Playground
Golang の Marshal() のソースを見ても、Marshal()
の引数に渡された型に問題がある場合にエラーを返すようになっています。Foo
のインスタンスが作成された時点で型は決まってしまうため、json.Marshal()
のエラー・ケースをテストできないのです。
type Foo int
を type Foo interface{}
などに変更できればいいのですが、やんごとなき事情でできません。
「"golang" json.Marshal テスト 失敗ケース
」でググっても、ドンピシャの記事がなかったので自分のググラビリティとして。
TL; DR (今北産業)
-
monkey patching
(モンキーパッチ)の手法を使う。 -
json.Marshal
を変数に代入し、それを使う。
テスト時には、変数の中身をモック(擬似処理するもの)と入れ替えて、強制的にエラーを出力させる。 - マスター、動くものをくれ (Go 1.18) 👇
サンプル・ソース
package main
import (
"bytes"
"encoding/json"
"io"
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
// json.Marshal is a monkey patch of json.Marshal to ease testing.
var jsonMarshal = json.Marshal
// ----------------------------------------------------------------------------
// Type: Foo
// ----------------------------------------------------------------------------
type Foo int
func (f Foo) Dump(w io.Writer) (n int, err error) {
if w == nil {
return 0, errors.New("dump failed: the io.Writer w is nil")
}
byteJSON, err := jsonMarshal(f) // <--- Monkey patched !!
if err != nil {
return 0, errors.Wrap(err, "dump failed: can not marshal NodeLog to JSON")
}
return w.Write(byteJSON)
}
// ----------------------------------------------------------------------------
// Tests
// ----------------------------------------------------------------------------
// 正常ケース
func TestFoo_Dump_golden(t *testing.T) {
bar := Foo(5963)
b := bytes.Buffer{}
_, err := bar.Dump(&b)
require.NoError(t, err)
require.Equal(t, "5963", b.String())
}
// 引数が nil だった場合
func TestFoo_Dump_arg_is_nil(t *testing.T) {
bar := Foo(5963)
n, err := bar.Dump(nil)
require.Error(t, err, "nil input should be an error")
require.Zero(t, n, "it should be zero on error")
require.Contains(t, err.Error(),
"dump failed: the io.Writer w is nil")
}
// マーシャリング(JSON 変換)に失敗した場合
func TestFoo_Dump_fail_marshaling(t *testing.T) {
// Backup and defer recover
oldJsonMarshal := jsonMarshal
defer func() {
jsonMarshal = oldJsonMarshal
}()
// Mock
jsonMarshal = func(v any) ([]byte, error) {
return nil, errors.New("forced error to marshal")
}
bar := new(Foo)
b := bytes.Buffer{}
n, err := bar.Dump(&b)
require.Error(t, err, "nil input should be an error")
require.Zero(t, n, "it should be zero on error")
require.Contains(t, err.Error(),
"forced error to marshal")
}
- オンラインで動作をみる @ Go Playground
TS; DR
下記の os.Exit
や os.Args
などのモックと同じ手法です。
参考文献
- encode_test.go | json | encoding | src @ cs.opensource.google
- How to write unit test for failure case of json.Marshall? @ StackOverflow
- Json unmarshal fails due to invalid characters @ StackOverflow