3

[Go] How to work with dates in tests

 1 year ago
source link: https://eltonminetto.dev/en/post/2023-06-30-mock-time-golang/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

[Go] How to work with dates in tests

Elton Minetto

June 30, 2023

Translations: Pt

Working with dates in any programming language presents some challenges. In this post, I will show how to work with dates when writing unit tests for a Go application.

Let’s go to the example:

import (
	"time"

	"github.com/google/uuid"
)

type Food struct {
	ID             uuid.UUID
	Name           string
	ExpirationDate time.Time
}

func canIEat(f Food) bool {
	if time.Now().Before(f.ExpirationDate) {
		return true
	}
	return false
}

One way to write a unit test for this code could be:

import (
	"testing"
	"time"

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

func TestCanIEat(t *testing.T) {
	f1 := Food{
		ExpirationDate: time.Now().AddDate(0, 0, 1),
	}
	assert.True(t, canIEat(f1))

	f2 := Food{
		ExpirationDate: time.Now().AddDate(0, 0, -1),
	}
	assert.False(t, canIEat(f2))
}

Another way to solve this is to create an abstraction for the time package. For this, we will create a new package called clock, inside it, we will add the clock.go file:

package clock

import "time"

// Clock interface
type Clock interface {
	Now() time.Time
}

// RealClock clock
type RealClock struct{}

// NewRealClock create a new real clock
func NewRealClock() *RealClock {
	return &RealClock{}
}

// Now returns the current data
func (c *RealClock) Now() time.Time {
	return time.Now()
}

The next step is to refactor the function that will use the new package:

import (
	"time"

	"github.com/eminetto/post-time/clock"
	"github.com/google/uuid"
)

type Food struct {
	ID             uuid.UUID
	Name           string
	ExpirationDate time.Time
}

func canIEat(c clock.Clock, f Food) bool {
	if c.Now().Before(f.ExpirationDate) {
		return true
	}
	return false
}

As the canIEat function receives the clock.Clock interface, we can, in our test, use a new implementation of this interface:

import (
	"testing"
	"time"

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

type FakeClock struct{}

func (c FakeClock) Now() time.Time {
	return time.Date(2023, 6, 30, 20, 0, 0, 0, time.Local)
}

func TestCanIEat(t *testing.T) {
	type test struct {
		food     Food
		expected bool
	}
	fc := FakeClock{}
	cases := []test{
		{
			food: Food{
				ExpirationDate: time.Date(2020, 10, 1, 20, 0, 0, 0, time.Local),
			},
			expected: false,
		},
		{
			food: Food{
				ExpirationDate: time.Date(2023, 6, 30, 20, 0, 0, 0, time.Local),
			},
			expected: false,
		},
		{
			food: Food{
				ExpirationDate: time.Date(2023, 6, 30, 21, 0, 0, 0, time.Local),
			},
			expected: true,
		},
	}
	for _, c := range cases {
		assert.Equal(t, c.expected, canIEat(fc, c.food))
	}
}

This way, we better control what will we are using in the test and gain performance because it is no longer necessary to do date calculations like the time.Now().AddDate(0, 0, 1) of the first example.

What I’m doing here is a simple tip, but it shows how powerful and easy to use the concept of interfaces in Go is.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK