Test時に値を書き換えて元に戻すサンプル

メルカリさんのこのブログを呼んで表題のテスト時に置ける値の一時的な書き換えとresetの方法がとても便利だったのでメモりました。

tech.mercari.com

テストを回すときに実際のURLでなく適当なダミーURLを叩いてHTTPのレスポンスをモックしたい場合があると思います。 その際によくやるのが、実際のURLをテスト時だけ書き換える処理です。テスト時のみ書き換えるに当たって、求められるのは

  • テスト中だけ動くこと
  • テストを抜けたら(完了したら)元のURLにresetすること

です。
これをgoのdeferを使って簡単に実現します。

deferを使った書き換え方法

サンプルコードは以下です。

// main.go
package main

import "fmt"

var rewriter = "hoge"

func main() {
    fmt.Println(rewriter)
}


// main_test.go
package main

import (
    "testing"
)

func TestSample(t *testing.T) {
    t.Run("rewrite test", func(t *testing.T) {
        defer rewriteString("fuga")()
    })
}

func rewriteString(s string) func() {
    var tmp string
    tmp, rewriter := rewriter, s
    return func() {
        rewriter = tmp
    }
}

subtest関数において

defer rewriteString("fuga")()

この1行で値の差し替えとresetを行なっています。

  • returnがfunc型であること
  • deferが呼ばれるのは関数 or サブテスト関数を抜ける時でreturnのfuncが呼ばれるのも同じタイミングであること

この2つの挙動をうまく使っているなと思いました。

最初、このコードを見たときにどうしてそのように動くのか全く理解できなかったんですが、これは

  • defer で rerwrite("fuga")を呼んだ時点で rewritehoge -> fugaに書き換わっている。
  • サブテスト関数内から抜ける(deferが呼ばれる)まで rewrite = fuga の状態になる。
  • deferで呼ばれるのは rewriteString メソッドのreturnのfunc。
  • サブテストを抜けるタイミングで return func() の中身が呼ばれるので、ここで変数がresetされる。

という挙動で意図した挙動になっていると理解しました。
改めて考えるとちゃんと動いていますが、これ実際に使われてるテクニックだとするととても綺麗な書き方だと思いました。

テスト時だけ特定の値に変数を書き換えるときに専用のメソッドを用意してましたが、テスト全体で共通で使えるメソッドを export_test.go に書いておいて、同一packageないから呼べるようにしておけばテストコードの重複もなさそうです。

使う場面は多々ありそうなので、これは今後使っていきたいtipsです。

載せるようなものでもないですが、一応、ってことでコードはこちら

github.com