O hirunewani blog

Q. gomockでフィールドの部分一致を検証したい

Created at

Custom matcherを利用するか、Doメソッド内で検証するか

前提:gomock.Eqについて

gomockでは、主にgomock.Eq()matcherを利用して値の検証を行える。

次のように直接値を指定したケースでも、内部的にはgomock.Eq()が利用されている。

repo.EXPECT().someMethod(2).Return(nil)

このgomock.Eq()では、値の完全一致が求められるため、複雑な構造体を受け取るような場合に全てを列挙するか、gomock.Any()を利用してスタブと化すかのどちらかになってしまう。

// throw error if there are missing or incorrect fields
repo.EXPECT().someMethod(chaosObject{
    SecondaryField: "value",
})

gomock.Any()を利用すると値の検証は行われない。

repo.EXPECT().someMethod(gomock.Any())

Q. 部分一致で検証したい

IDやタイムスタンプなど動的な値が含まれる場合や、関心事が特定のフィールドのみの場合、部分一致で検証したいことがある。

動的な値に関しては、それを生成する関数をモック化するという手段でも対応できると思われる。

A. Custom matcherを利用する

gomockが提供しているgomock.Matcherを利用して、任意のmatcherを作成することができる。

例えば、次のようにcmpを利用して部分一致を検証するmatcherを作成することができる。

import (
    "github.com/golang/mock/gomock"
    "github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

type someObjMatcher struct {
    expected someObj
}

func (m *someObjMatcher) Matches(x interface{}) bool {
    actual, ok := x.(someObj)
    if !ok {
        return false
    }

    return cmp.Equal(actual, m.expected, cmpopts.IgnoreFields(someObj{}, "Field1"))
}

次のように利用できる。

repo.EXPECT().someMethod(someObjMatcher{
    expected: someObj{
        Field1: "value",
    },
})

次の記事にあるように汎用的なmatcherを作成することもできる。

https://cloudandbuild.jp/blog/article-5#カスタムmatcherを作る

A. Doメソッド内で検証する

gomock.Matcherを利用する方法は、matcherを作成するためのコードが必要になるため、煩雑になることがある。

gomock.Do()内でassetionを行うことでシンプルに記述できる。

repo.EXPECT().someMethod(gomock.Any()).Do(func(arg someObj) {
    assert.Equal(t, "value", arg.Field1)
})

ただし、特定のオブジェクトに対する検証が複数箇所で必要になるようなケースでは、Custom matcherの方が適していると思われる。