メルカリさんのこのブログを呼んで表題のテスト時に置ける値の一時的な書き換えとresetの方法がとても便利だったのでメモりました。
テストを回すときに実際の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")を呼んだ時点で rewrite は hoge -> fugaに書き換わっている。
- サブテスト関数内から抜ける(deferが呼ばれる)まで rewrite = fuga の状態になる。
- deferで呼ばれるのは rewriteString メソッドのreturnのfunc。
- サブテストを抜けるタイミングで return func() の中身が呼ばれるので、ここで変数がresetされる。
という挙動で意図した挙動になっていると理解しました。
改めて考えるとちゃんと動いていますが、これ実際に使われてるテクニックだとするととても綺麗な書き方だと思いました。
テスト時だけ特定の値に変数を書き換えるときに専用のメソッドを用意してましたが、テスト全体で共通で使えるメソッドを export_test.go
に書いておいて、同一packageないから呼べるようにしておけばテストコードの重複もなさそうです。
使う場面は多々ありそうなので、これは今後使っていきたいtipsです。
載せるようなものでもないですが、一応、ってことでコードはこちら