📌

Goでtime.Now()を使用している箇所のテストについて

に公開

概要

time.Now()を使用している箇所のテストがしづらいと感じたのでその際のメモ

結論:contextにtime.Now()で生成する方法を採用

既存のコード

func Hoge() {
	todo := GetTodo()
	if IsTimeAfter(todo.StartTime) {
		...
	}
}

func IsTimeAfter(startTime time.Time) {
	currentDate := time.Now() // テストを実行する時刻に依存する。現在日時を固定にしたい。
	return startTime.After(currentDate)
}

対応方法

time.Now()で生成した現在時刻を引数にする

  • パッと修正可能
  • IsTimeAfter()のテストはしやすくなったが、Hoge()がtime.Nowに依存している状態
func Hoge() {
	todo := GetTodo()
	currentDate := time.Now()
	if IsTimeAfter(todo.StartTime, currentDate) {
		...
	}
}

func IsTimeAfter(startTime, currentDate time.Time) {
	return startTime.After(currentDate)
}

time.Now()で現在時刻を生成する処理をmock化

  • インターフェースを作成してテストの際はmockを使用する
  • 現在時刻を生成するためだけにインターフェースを定義したり、依存性を注入するのは少し過剰か?
type TimeProvider interface { // インターフェースを作成
    Now() time.Time
}

type RealTimeProvider struct{}

func (rtp RealTimeProvider) Now() time.Time { // 実装
    return time.Now()
}
type SomeService struct {
    TimeProvider timeprovider.TimeProvider
}

func NewSomeService(tp timeprovider.TimeProvider) *SomeService {
    return &SomeService{TimeProvider: tp}
}

func (s *SomeService) Hoge() {
    currentDate := s.TimeProvider.Now() // テストの際はモックを使用する
		if IsTimeAfter(todo.StartTime, currentDate) {
				...
		}
}

func IsTimeAfter(startTime, currentDate time.Time) {
	return startTime.After(currentDate)
}

middlewareでtime.Now()で現在時刻を生成してcontextに入れておく

  • mock化より実装はシンプルになる
// middleware(echoを使用)
func NewCurrentTime() echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			n := time.Now()
			ctx := context.WithValue(c.Request().Context(), "current_time", n)
			c.SetRequest(c.Request().WithContext(ctx))
			return next(c)
		}
	}
}
// route.go(echoを使用)
func SetupRouter() *echo.Echo {
	e := echo.New()
	e.Use(middleware.NewCurrentTime()) // ここで使用
	e.GET("/hoge", hogeHandler.Hoge)
	.
	.
func (s *SomeService) Hoge(ctx context.Context) {
    currentDate := ctx.Value("current_time").(time.Time) // ここでcontextから取得
		if IsTimeAfter(todo.StartTime, currentDate) {
				...
		}
}

// 現在時刻を引数にしているが、関数内でcontextから時刻を取得してもよい。迷う。
func IsTimeAfter(startTime, currentDate time.Time) {
	return startTime.After(currentDate)
}

参考にした記事

GitHubで編集を提案

Discussion