Shogo's Blog

Dec 10, 2025 - 2 minute read - go golang

Goのsynctestを使おうとしたらsynctest.Run not supported with asynctimerchanと怒られたときの対処法

TL;DR

  • Go 1.25 から正式サポートされたtesting/synctestパッケージを使ってみた
  • 「synctest.Run not supported with asynctimerchan」というメッセージとともにpanicした
  • go mod edit -go=1.23 を実行すれば解決

背景

shogo82148/go-retry は指数的バックオフをよしなになってくれるライブラリーです。

は待ち時間を調整するテストを書くために、「実際にSleepをして経過時間を取得する」という方法を採用しています。

// テストのイメージ
func Test(t *testing.T) {
  var policy = retry.Policy{
    MinDelay: 100 * time.Millisecond,
    MaxDelay: time.Second,
    MaxCount: 3,
  }

  // 実際にSleepをして経過時間を取得する
  start := time.Now()
  _ = policy.Do(t.Context(), func() error {
    return errors.New("some error")
  })
  d := time.Since(start)

  if d < 300*time.Millisecond {
    t.Errorf("want at least 300ms, got %s", d)
  }
}

このテストは実際に300ミリ秒Sleepしてしまうので、テストに必ず300ミリ秒かかってしまいます。 また実際のSleep時間はランタイムの状態によって変化してしまうため、 「300ミリ秒ちょうどSleepする」ことをテストしたいのに、実際にそのようなテストを書くことは難しいです。

testing/synctest を使ってみた

そんな問題を一気に解決してくれるのが、Go 1.25 から正式サポートされた testing/synctest です。 やり方は簡単。synctest.Test(t, func(t *testing.T) { /* ... */ }) で囲ってあげるだけです。

func Test(t *testing.T) {
  synctest.Test(t, func(t *testing.T) {
    var policy = retry.Policy{
      MinDelay: 100 * time.Millisecond,
      MaxDelay: time.Second,
      MaxCount: 3,
    }

    start := time.Now()
    _ = policy.Do(t.Context(), func() error {
      return errors.New("some error")
    })
    d := time.Since(start)

    // 300msちょうどSleepすることをテストできる!!
    if d != 300*time.Millisecond {
      t.Errorf("want 300ms, got %s", d)
    }
  })
}

と思っていた時期が僕にもありました。

=== RUN   Test
--- FAIL: Test (0.00s)
panic: synctest.Run not supported with asynctimerchan!=0 [recovered, repanicked]

なんと synctest.Run はサポートされていないと怒られてしまいました。

Go 1.23 で入ったタイマーの変更

asynctimerchan というのは Go 1.23 で入ったタイマーの挙動変更をコントロールするためのフラグです。

詳しい変更内容はリンク先に譲りますが、asynctimerchan=1 になっていると Go 1.22 以前の挙動に巻き戻してくれます。

そこで go.mod を確認してみると・・・

// go.mod
module github.com/shogo82148/go-retry/v2
 
go 1.18 // こいつが悪さをしていた

go ディレクティブが 1.18 になっているので、Goのランタイムは Go 1.18 の挙動をなるべく再現しようとします。 その結果 asynctimerchan=1 になってしまったのです。

解決方法

修正は簡単で、go mod edit -go=1.23 を実行して go.mod の go ディレクティブを書き換えれば解決します。

 module github.com/shogo82148/go-retry/v2
 
-go 1.18
+go 1.23

ただしタイマーの挙動が Go 1.23 以降のものになるので、そこだけ注意が必要です。

まとめ

testing/synctest を使うには Go 1.22 との互換性を切る必要があります。 ただしタイマーの挙動が変わってしまうので注意しましょう。

🐰 新しきポストが加わり、喜びあふれ、
Go言語の迷宮も解き明かす道筋、
synctest、asynctimer、謎は晴れて、
開発者の手助け、ウサギのちから!
✨ by CodeRabbit

参考