https://qiita.com/cpp0302/items/3c5254b840df6af24c10
and factory Advent Calendar 2020Day 22
この記事は and factory Advent Calendar 2020 の22日目の記事です。
私のチームでは、DIをする際にgoogle/wire
を使用しているのですが、モックの使い方について調べたところ、公式のGitHubにて、ベストプラクティスのページに記載がありましたので、それを日本語で解説しようと思います。
なお、本記事ではgoogle/wire
そのものの説明はしません。こちらに詳しく紹介されていますのでそちらを参照してみてください。
公式ではモックを注入するためのアプローチとして2つ紹介されていますので、両方について解説します。
解説を始める前にモックなしの状態のサンプルソースを載せておきます。 サイコロを振って旅の行き先を決めるという簡単なサンプルソースを作成しました。
main.go
package main
import (
"fmt"
"math/rand"
"strconv"
"time"
)
// App アプリケーション
type App struct {
DiceTrip DiceTrip
}
// DiceTrip サイコロの旅
type DiceTrip struct {
Dice Dice
}
// Decide 旅の行き先を決める
func (dt DiceTrip) Decide() string {
n := dt.Dice.Roll()
str := strconv.Itoa(n) + ": "
if n == 6 {
str += "深夜バスで福岡へ"
} else {
str += "飛行機で新千歳空港へ"
}
return str
}
// Dice サイコロ
type Dice interface {
Roll() int
}
// RealDice Diceインタフェースを実装したもの
type RealDice struct {
}
// Roll サイコロを振る(擬似乱数を使用)
func (ds RealDice) Roll() int {
return rand.Intn(6) + 1
}
func main() {
rand.Seed(time.Now().UnixNano())
app := InitApp()
fmt.Println(app.DiceTrip.Decide())
}
wire.go
//+build wireinject
package main
import "github.com/google/wire"
// InitApp Appを生成する
func InitApp() *App {
wire.Build(
wire.Struct(new(App), "*"),
wire.Struct(new(DiceTrip), "*"),
wire.InterfaceValue(new(Dice), RealDice{}),
)
return nil
}
wire_gen.go
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
package main
// Injectors from wire.go:
// InitApp Appを生成する
func InitApp() *App {
dice := _wireRealDiceValue
diceTrip := DiceTrip{
Dice: dice,
}
app := &App{
DiceTrip: diceTrip,
}
return app
}
var (
_wireRealDiceValue = RealDice{}
)
実際に解説に入る前に、モックを注入するために特別なことをしなければいけない理由について述べておきます。
テストでモックを使用する場合、テストを実施する前に、モックがすべき動作をあらかじめ指定しておく必要があるはずです。
ですが、通常と同様にwire.Build
内でモックを生成して返してしまうと、当然インタフェースとして返ってきますのでモックを直接操作することができません。
よって、モックを注入する場合、以下の両方の条件を満たす必要があります。
サンプルソースの場合、DiceTrip.Decide
の出力結果をテストするのであれば、Dice.Roll
の結果をランダムではなく、特定の値が返ってくるようにモックを作成したいです。