Help us understand the problem. What is going on with this article?

Testifyでmockを作ってテストを記述してみたメモ

More than 1 year has passed since last update.

はじめに

Testifyというテストライブラリでテストを記述する際にモックを用意してテストする方法が感覚的にわからなかったので、実装して理解しようと思いました。
加えてたぶん数日したら、忘れそうな気がしたので忘備録も兼ねてます。
それから誰かの助けになれば、と思って公開します。

前提条件

以下のような環境でテストしてみました。

go 1.12.1
mac os 10.13.2(High Sierra)

事前準備

適当なところに検証用のディレクトリを作り、モジュールを定義します。
今回はテストライブラリなので、用意するファイルはmain.goとmain_test.goの2つ。
main.goは何らかの処理を行い、main_test.goはテストコードになります。

mkdir test
cd test
go mod init test
touch main.go main_test.go

testifyのインストール

go getでインストール可能。
ちなみに公式サイトはこちら

go get github.com/stretchr/testify

まずはプログラムの実装

どこかの天気配信サービスに天気をリクエストして天気を取得する、みたいな感じのものを作ります。
実際の天気配信サービスにアクセスするのは大変なので、単に文字列を返却するだけの、簡単なプログラムにします。

処理の流れとしては

WeatherService.GetWeather() -> WeatherClient.RequestWeather()

みたいな感じです。

main.go
package main

import (
    "fmt"
)

// お天気クライアントのinterface
type WeatherClient interface {
    RequestWeather() string
}

// お天気クライアントの実態の構造体
type WeatherClientImpl struct{}

// お天気をリクエストする関数
func (c WeatherClientImpl) RequestWeather() string {
    fmt.Println("天気をリクエストするクライアント")
    // 本当は天気をリクエストする処理とかがここに入る
    return "晴れ"
}

// お天気を扱うサービスの構造体(interfaceの実態)
type WeatherService struct {
    client WeatherClient
}

// お天気取得関数
func (w WeatherService) GetWeather() string {
    fmt.Println("天気を取得する")
    return w.client.RequestWeather()
}

func main() {
    weatherClient := WeatherClientImpl{}
    weatherService := WeatherService{client: weatherClient}
    weather := weatherService.GetWeather()
    fmt.Printf("天気は%s\n", weather)
}

プログラムの実行

実行すると以下のような感じの文字列が出力されます。

❯ go run main.go

天気を取得する
天気をリクエストするクライアント
天気は晴れ

テストコードの記述

次にテストコードを以下のように記述します。
このテストではモックのClientに"くもり"を返却するように指定して、"くもり"が変革されることを期待するテストを行っています。

main_test.go
package main

import (
    "fmt"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

// モック(今回の場合はTokyoWeatherClientのモック)
type weatherClientMock struct {
    mock.Mock
}

// モック関数定義
func (mock *weatherClientMock) RequestWeather() string {
    fmt.Println("モック実行")
    result := mock.Called() // 呼び出し
    return result.String(0) // モックの返却値 Returnの引数を返却する
}

// くもりの時のテスト
func TestRequestWeather1(t *testing.T) {
    assert := assert.New(t)

    // モック準備
    weatherClientMock := new(weatherClientMock)
    weatherClientMock.On("RequestWeather").Return("くもり")   // このテストではモックに"くもり"を返却するように指定している

    // テスト実行
    weatherService := WeatherService{client: weatherClientMock}
    weather := weatherService.GetWeather()

    // 検証
    assert.Equal(weather, "くもり") // 天気サービスが"くもり"を返却しているか検証
}

テストの実行

以下のようにテストを実行することでテストが無事にパスすると思います。

❯ go test
天気を取得する
モック実行
天気を取得する
モック実行
PASS
ok      _/test  0.016s

ポイント

ポイントは以下の2つかなと思います。

  • WeatherClientをinterfaceとして定義している。こうすることでWeatherServiceではClientの実態を気にしなくても良くなる。
  • WeatherServiceを初期化する際にclient: weatherClientを渡している。外部からclientを与えるようにすることでテスト時にモックに差し替えられます。

参考

公式
Go言語でテスト作成 testifyの基本的な使い方
testify/mockでgolangのテストを書く

takeshi_miyajim
東京都で自営でプログラマをしています。 お仕事などがありましたら、ご連絡いただけたらと思います。
http://miyazi888.hatenablog.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away