Skip to content

Commit

Permalink
First (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
michalkurzeja authored Sep 30, 2019
1 parent d829abc commit 912a519
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
28 changes: 28 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
sudo: false

language: go

go:
- "1.13.x"
- "1.x"

env:
- GO111MODULE=on

cache:
directories:
- $GOPATH/pkg/mod

before_install:
- go get -u golang.org/x/lint/golint
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls

script:
- golint ./...
- go vet ./...
- go test -covermode=count -coverprofile=profile.cov ./...
- goveralls -coverprofile=profile.cov -service=travis-pro

notifications:
email: false
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include github.com/msales/make/golang
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,42 @@
# go-clock
[![Build Status](https://travis-ci.com/msales/go-clock.svg?token=jnuRixQ5JT2Tqqcethpp&branch=master)](https://travis-ci.com/msales/go-clock)
[![Coverage Status](https://coveralls.io/repos/github/msales/go-clock/badge.svg?branch=master&t=x6DuOO)](https://coveralls.io/github/msales/go-clock?branch=first)

A small package that offers testable time functions.

## Why?
It is hard to properly test the code that performs time operations relative to the current time
or that depends on the time flow, e.g. with `time.Now()` or `time.After(d)` calls.

`go-clock` provides a package-level Clock object that can easily be swapped for a configurable mock in your tests.
The package also offers some commonly-used functions from the `time` package that use the `Clock`.

## Installation
```shell script
go get github.com/msales/go-clock
```

## Usage
In your code, simply use the `go-clock` functions for time retrieval instead of the standard `time` package:

```go
import "github.com/msales/go-clock"

now := clock.Now() // Instead of `time.Now()`
since := clock.Since(now) // Instead of `time.Since()`
c := clock.After(time.Second) // Instead of `time.After(time.Second)`
```

In your tests, you can mock the clock to get predictable time output:

```go
fakeNow := time.Date(...)

mock := clock.Mock(fakeNow) // `clock.Now()` will always return `fakeNow` time.
defer clock.Restore()

mock.Add(time.Second) // Advances the fake clock's time by a second.
```

`go-clock` uses the clock implementation from the [benbjohnson/clock](https://github.com/benbjohnson/clock) package.
For more details on the usage of the clock, please see it's docs.
71 changes: 71 additions & 0 deletions clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package clock

import (
"time"

"github.com/benbjohnson/clock"
)

// init initializes the Clock variable with a real Clock.
func init() {
Restore()
}

// Clock represents a global clock.
var Clock clock.Clock

// After waits for the duration to elapse and then sends the current time
func After(d time.Duration) <-chan time.Time {
return Clock.After(d)
}

// AfterFunc waits for the duration to elapse and then calls f in its own goroutine.
func AfterFunc(d time.Duration, f func()) *clock.Timer {
return Clock.AfterFunc(d, f)
}

// Now returns the current local time.
func Now() time.Time {
return Clock.Now()
}

// Since returns the time elapsed since t.
func Since(t time.Time) time.Duration {
return Clock.Since(t)
}

// Sleep pauses the current goroutine for at least the duration d.
func Sleep(d time.Duration) {
Clock.Sleep(d)
}

// Tick is a convenience wrapper for NewTicker providing access to the ticking channel only.
func Tick(d time.Duration) <-chan time.Time {
return Clock.Tick(d)
}

// Ticker returns a new Ticker containing a channel that will send the
// time with a period specified by the duration argument.
func Ticker(d time.Duration) *clock.Ticker {
return Clock.Ticker(d)
}

// Timer creates a new Timer that will send the current time on its channel after at least duration d.
func Timer(d time.Duration) *clock.Timer {
return Clock.Timer(d)
}

// Mock replaces the Clock with a mock frozen at the given time and returns it.
func Mock(now time.Time) *clock.Mock {
mock := clock.NewMock()
mock.Set(now)

Clock = mock

return mock
}

// Restore replaces the Clock with the real clock.
func Restore() {
Clock = clock.New()
}
179 changes: 179 additions & 0 deletions clock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package clock_test

import (
"testing"
"time"

clock2 "github.com/benbjohnson/clock"
"github.com/msales/go-clock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestMock(t *testing.T) {
now := time.Date(2019, time.September, 30, 14, 30, 00, 00, time.UTC)

mock := clock.Mock(now)

time.Sleep(time.Nanosecond) // We just want to be sure that ANY time has passed.
assert.Equal(t, now, clock.Now())

mock.Add(time.Second)
assert.Equal(t, now.Add(time.Second), clock.Now())
}

func TestRestore(t *testing.T) {
now := time.Date(2019, time.September, 30, 14, 30, 00, 00, time.UTC)

clock.Mock(now)
clock.Restore()

time.Sleep(time.Nanosecond) // We just want to be sure that ANY time has passed.
assert.NotEqual(t, now, clock.Now())
}

func TestAfter(t *testing.T) {
d := time.Second
ch := make(<-chan time.Time)

clk := new(mockClock)
clk.On("After", d).Return(ch)
clock.Clock = clk

got := clock.After(d)

clk.AssertExpectations(t)
assert.Equal(t, ch, got)
}

func TestAfterFunc(t *testing.T) {
d := time.Second
f := func() {}
timer := &clock2.Timer{}

clk := new(mockClock)
clk.On("AfterFunc", d, mock.AnythingOfType("func()")).Return(timer)
clock.Clock = clk

got := clock.AfterFunc(d, f)

clk.AssertExpectations(t)
assert.Equal(t, timer, got)
}

func TestNow(t *testing.T) {
now := time.Now()

clk := new(mockClock)
clk.On("Now").Return(now)
clock.Clock = clk

got := clock.Now()

clk.AssertExpectations(t)
assert.Equal(t, now, got)
}

func TestSince(t *testing.T) {
now := time.Now()
d := time.Second

clk := new(mockClock)
clk.On("Since", now).Return(d)
clock.Clock = clk

got := clock.Since(now)

clk.AssertExpectations(t)
assert.Equal(t, d, got)
}

func TestSleep(t *testing.T) {
d := time.Second

clk := new(mockClock)
clk.On("Sleep", d)
clock.Clock = clk

clock.Sleep(d)

clk.AssertExpectations(t)
}

func TestTick(t *testing.T) {
d := time.Second
ch := make(<-chan time.Time)

clk := new(mockClock)
clk.On("Tick", d).Return(ch)
clock.Clock = clk

got := clock.Tick(d)

clk.AssertExpectations(t)
assert.Equal(t, ch, got)
}

func TestTicker(t *testing.T) {
d := time.Second
ticker := &clock2.Ticker{}

clk := new(mockClock)
clk.On("Ticker", d).Return(ticker)
clock.Clock = clk

got := clock.Ticker(d)

clk.AssertExpectations(t)
assert.Equal(t, ticker, got)
}

func TestTimer(t *testing.T) {
d := time.Second
timer := &clock2.Timer{}

clk := new(mockClock)
clk.On("Timer", d).Return(timer)
clock.Clock = clk

got := clock.Timer(d)

clk.AssertExpectations(t)
assert.Equal(t, timer, got)
}

type mockClock struct {
mock.Mock
}

func (m *mockClock) After(d time.Duration) <-chan time.Time {
return m.Called(d).Get(0).(<-chan time.Time)
}

func (m *mockClock) AfterFunc(d time.Duration, f func()) *clock2.Timer {
return m.Called(d, f).Get(0).(*clock2.Timer)
}

func (m *mockClock) Now() time.Time {
return m.Called().Get(0).(time.Time)
}

func (m *mockClock) Since(t time.Time) time.Duration {
return m.Called(t).Get(0).(time.Duration)
}

func (m *mockClock) Sleep(d time.Duration) {
m.Called(d)
}

func (m *mockClock) Tick(d time.Duration) <-chan time.Time {
return m.Called(d).Get(0).(<-chan time.Time)
}

func (m *mockClock) Ticker(d time.Duration) *clock2.Ticker {
return m.Called(d).Get(0).(*clock2.Ticker)
}

func (m *mockClock) Timer(d time.Duration) *clock2.Timer {
return m.Called(d).Get(0).(*clock2.Timer)
}
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/msales/go-clock

go 1.13

require (
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3
github.com/stretchr/testify v1.4.0
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3 h1:wOysYcIdqv3WnvwqFFzrYCFALPED7qkUGaLXu359GSc=
github.com/benbjohnson/clock v0.0.0-20161215174838-7dc76406b6d3/go.mod h1:UMqtWQTnOe4byzwe7Zhwh8f8s+36uszN51sJrSIZlTE=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

0 comments on commit 912a519

Please sign in to comment.