Goのユニットテストでどうしてもaws-sdkを叩くレイヤーのtestがしたくなったとき、localstackなど実際のawsを模した機能を用意できればそれが一番だが、たいてい大掛かりになってしまう。
goのaws-sdkには大抵interfaceが用意されているので、これをmockでうまく使うとなかなか良かった。
そもそもDIの基本になるが、なるべくs3.S3などのstructそのものでなく、このようなinterfaceへの依存を持っておくことで簡単にmockが可能になるので、設計の際に留意しておく。
例えば、s3であれば S3APIというinterfaceがある。
今回はこういうものを用意した。
package aws_sdk
import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/s3"
)
type S3SdkMockImpl struct {
//テストごとに差し替え可能にする
ListObjects_Mock func(*s3.ListObjectsInput) (*s3.ListObjectsOutput, error)
HeadObject_Mock func(*s3.HeadObjectInput) (*s3.HeadObjectOutput, error)
}
// auto generated by jetbrains
func (S3SdkMockImpl) AbortMultipartUpload(*s3.AbortMultipartUploadInput) (*s3.AbortMultipartUploadOutput, error) {
panic("implement me")
}
func (S3SdkMockImpl) AbortMultipartUploadWithContext(aws.Context, *s3.AbortMultipartUploadInput, ...request.Option) (*s3.AbortMultipartUploadOutput, error) {
panic("implement me")
}
func (S3SdkMockImpl) AbortMultipartUploadRequest(*s3.AbortMultipartUploadInput) (*request.Request, *s3.AbortMultipartUploadOutput) {
panic("implement me")
}
func (S3SdkMockImpl) CompleteMultipartUpload(*s3.CompleteMultipartUploadInput) (*s3.CompleteMultipartUploadOutput, error) {
panic("implement me")
}
func (S3SdkMockImpl) CompleteMultipartUploadWithContext(aws.Context, *s3.CompleteMultipartUploadInput, ...request.Option) (*s3.CompleteMultipartUploadOutput, error) {
panic("implement me")
}
...
// ダミーが刺されていれば使う
func (self *S3SdkMockImpl) HeadObject(in *s3.HeadObjectInput) (*s3.HeadObjectOutput, error) {
if self.HeadObject_Mock != nil {
return self.HeadObject_Mock(in)
}
panic("implement me")
}
....
func (self *S3SdkMockImpl) ListObjects(in *s3.ListObjectsInput) (*s3.ListObjectsOutput, error) {
if self.ListObjects_Mock != nil {
return self.ListObjects_Mock(in)
}
panic("implement me")
}
interfaceを埋めるメソッド自体はGolang等のIDEですぐにgenerateできるが、テストケースごとに生成するのも見通しが悪いため、必要なメソッドごとに差し替え可能にし、使いまわせるようにした。
使用側はこうなる。
s3SdkMockTestCase01 := aws_sdk.S3SdkMockImpl{
ListObjects_Mock: func(input *s3.ListObjectsInput) (output *s3.ListObjectsOutput, e error) {
return &s3.ListObjectsOutput{
... //自由に返す
} , nil
},
}
s3SdkMockTestCase02 := aws_sdk.S3SdkMockImpl{
ListObjects_Mock: func(input *s3.ListObjectsInput) (output *s3.ListObjectsOutput, e error) {
return &s3.ListObjectsOutput{
... //自由に返す
} , nil
},
}
s3DataRepoisitoryForTest01 := NewS3DataRepository(s3SdkMockTestCase01)
s3DataRepoisitoryForTest02 := NewS3DataRepository(s3SdkMockTestCase02)
場合に応じて差し替えが簡単になり、使いまわせるようにもなった。
追記
interface自体を埋め込んであげるともっといいですね...