-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d829abc
commit 912a519
Showing
8 changed files
with
343 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
include github.com/msales/make/golang |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |