0
0

More than 1 year has passed since last update.

【Golang】json.Marshal のエラーをテストする【json.MarshalIndent 含む失敗ケースの作成】

Last updated at Posted at 2022-07-08

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)
}

Golang の Marshal() のソースを見ても、Marshal()引数に渡された型に問題がある場合にエラーを返すようになっています。Foo のインスタンスが作成された時点で型は決まってしまうため、json.Marshal() のエラー・ケースをテストできないのです。

type Foo inttype Foo interface{} などに変更できればいいのですが、やんごとなき事情でできません。

"golang" json.Marshal テスト 失敗ケース」でググっても、ドンピシャの記事がなかったので自分のググラビリティとして。

TL; DR (今北産業)

  1. monkey patchingモンキーパッチ)の手法を使う。
  2. json.Marshal を変数に代入し、それを使う。
    テスト時には、変数の中身をモック(擬似処理するもの)と入れ替えて、強制的にエラーを出力させる。
  3. マスター、動くものをくれ (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")
}

TS; DR

下記の os.Exitos.Args などのモックと同じ手法です。

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0