Categorygithub.com/derision-test/glock
modulepackage
1.1.0
Repository: https://github.com/derision-test/glock.git
Documentation: pkg.go.dev

# README

Glock

PkgGoDev Build status Latest release

Small go library for mocking parts of the time and context packages.

Time Utilities

The package contains a Clock and Ticker interface that wrap the time.Now, time.After, and time.Sleep functions and the Ticker struct, respectively.

A real clock can be created for general (non-test) use. This implementation simply falls back to the functions provided in the time package.

clock := glock.NewRealClock()
clock.Now()                       // calls time.Now
clock.After(time.Second)          // calls time.After(time.Second)

t := clock.NewTicker(time.Second) // wraps time.NewTicker(time.Second)
t.Chan()                          // returns ticker's C field
t.Stop()                          // stops the ticker

In order to make unit tests that depend on time deterministic (and free of sleep calls), a mock clock can be used in place of the real clock. The mock clock allows you to control the current time with SetCurrent and Advance methods.

clock := glock.NewMockClock()

clock.Now() // returns time of creation
clock.Now() // returns time of creation
clock.SetCurrent(time.Unix(603288000, 0))
clock.Now() // returns Feb 12, 1989
clock.Advance(time.Day)
clock.Now() // returns Feb 13, 1989

The Advance method will also trigger a value on the channels created by the After and Ticker functions, if enough virtual time has elapsed for the events to fire.

clock := glock.NewMockClockAt(time.Unix(603288000, 0))

c1 := clock.After(time.Second)
c2 := clock.After(time.Minute)
clock.GetAfterArgs()            // returns {time.Second, time.Minute}
clock.GetAfterArgs()            // returns {}
clock.Advance(time.Second * 30) // Fires c2
clock.Advance(time.Second * 30) // Fires c1
clock := glock.NewMockClock()

ticker := clock.NewTicker(time.Minute)
defer ticker.Stop()

go func() {
    for range ticker.Chan() {
        // ...
    }
}()

clock.Advance(time.Second * 30)
clock.Advance(time.Second * 30) // Fires ch
clock.Advance(time.Second * 30)
clock.Advance(time.Second * 30) // Fires ch

The Advance method will send a value to any current listener registered to a channel on the clock. Timing these calls in relation with the clock consumer is not always an easy task. A variation of the advance method, BlockingAdvance can be used in its place when you want to first ensure that there is a listener on a channel returned by After.

clock := glock.NewMockClock()

go func() {
    <-clock.After(time.Second * 30)
}()

clock.BlockingAdvance(time.Second * 30) // blocks until the concurrent call to After
clock.BlockingAdvance(time.Second * 30) // blocks indefinitely as there are no listeners

Ticker instances themselves have the same time advancing mechanisms. Using Advance on a ticker (or using Advance on the clock from which a ticker was created) will cause the ticker to fire once and then forward itself to the current time. This mimics the behavior of the Go runtime clock (see the test functions ^TestTickerOffset).

Where the Advance method sends the ticker's time to the consumer in a background goroutine, the BlockingAdvance variant will send the value in the caller's goroutine.

ticker := clock.NewMockTicker(time.Second * 30)
defer ticker.Stop()

go func() {
    <-ticker.Chan()
    <-ticker.Chan()
    <-ticker.Chan()
}()

ticker.BlockingAdvance(time.Second * 15)
ticker.BlockingAdvance(time.Second * 15) // Fires ch
ticker.BlockingAdvance(time.Second * 15)
ticker.BlockingAdvance(time.Second * 15) // Fires ch
ticker.BlockingAdvance(time.Second * 60) // Fires ch _once_

ticker.Advance(time.Second * 30)         // does not block; sent asynchronously
ticker.BlockingAdvance(time.Second * 30) // blocks indefinitely as there are no listeners

Context Utilities

If you'd like to use a context.Context as a way to make a glock Clock available, this package provides WithContext and FromContext utility methods.

To add a Clock to a context you would call WithContext and provide a parent context as well as the Clock you'd like to add.

clock := glock.NewMockClock()

ctx := context.Background()
ctx = glock.WithContext(ctx, clock)

To retrieve the Clock from a context, the FromContext method is available. If a Clock does not already exist within the context FromContext will return a new real clock instance.

// Retrieve a mock clock from the context
clock := glock.NewMockClock()

ctx := context.Background()
ctx = glock.WithContext(ctx, clock)

ctxClock := glock.FromContext(ctx)
// Retrieve a default real clock from the context
ctx := context.Background()
ctx = glock.WithContext(ctx, clock)

ctxClock := glock.FromContext(ctx)

Context Testing Utilities

The package also contains the functions ContextWithDeadline and ContextWithTimeout that mimic the context.WithDeadline and context.WithTimeout functions, but will use a user-provided Clock instance rather than the standard time.After function.

A real clock can be used for non-test scenarios without much additional overhead.

clock := glock.NewRealClock()
ctx, cancel := glock.ContextWithTimeout(context.Background(), clock, time.Second)
defer cancel()

<-ctx.Done() // Waits 1s

In order to make unit tests that depend on context timeouts deterministic, a mock clock can be used in place of the real clock. The mock clock can be advanced in the same was a described in the previous section.

clock := glock.NewMockClock()
ctx, cancel := glock.ContextWithTimeout(context.Background(), clock, time.Second)
defer cancel()

go func() {
    <-time.After(time.Millisecond * 250)
    clock.BlockingAdvance(time.Second)
}()

<-ctx.Done() // Waits around 250ms

# Functions

ContextWithDeadline mimmics context.WithDeadline, but uses the given clock instance instead of the using standard time.After function directly.
ContextWithTimeout mimmics context.WithTimeout, but uses the given clock instance instead of the using standard time.After function directly.
FromContext retrieves the Clock value from the provided context.
NewMockClock creates a new MockClock with the internal time set to time.Now().
NewMockClockAt creates a new MockClick with the internal time set to the given time.
NewMockTicker creates a new MockTicker with the internal time set to time.Now().
NewMockTickerAt creates a new MockTicker with the internal time set to the given time.
NewMockTimer creates a new MockTimer with the internal time set to time.Now().
NewMockTimerAt creates a new MockTimer with the internal time set to the given time.
NewRealClock returns a Clock whose implementation falls back to the methods available in the time package.
No description provided by the author
No description provided by the author
WithContext returns a context derived from the provided context with the provided Clock as a value.

# Structs

MockClock is an implementation of Clock that can be moved forward in time in increments for testing code that relies on timeouts or other time-sensitive constructs.
MockTicker is an implementation of Ticker that can be moved forward in time in increments for testing code that relies on timeouts or other time-sensitive constructs.
MockTimer is an implementation of Timer that can be moved forward in time in increments for testing code that relies on timeouts or other time-sensitive constructs.

# Interfaces

No description provided by the author
Clock is a wrapper around common functions in the time package.
Ticker is a wrapper around a time.Ticker, which allows interface access to the underlying channel (instead of bare access like the time.Ticker struct allows).
Timer is a wrapper around a time.Timer, which allows interface access to the underlying Timer.