DEV Community

Elton Minetto
Elton Minetto

Posted on

4

[Go] How to work with dates in tests

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
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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))
    }
}

Enter fullscreen mode Exit fullscreen mode

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.

AWS Security LIVE!

Join us for AWS Security LIVE!

Discover the future of cloud security. Tune in live for trends, tips, and solutions from AWS and AWS Partners.

Learn More

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more