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の結果をランダムではなく、特定の値が返ってくるようにモックを作成したいです。