
Mocking time and testing event loops in Go - zdw
https://dmitryfrank.com/articles/mocking_time_in_go
======
jrockway
When I programmed Java I injected a lot of clocks and didn't find it to be
that terrible. I find myself not doing that in Go, instead preferring to leave
timekeeping out of as much code as possible, and letting the calling code set
the time constraints.

Typically you would just do this with contexts. I was going to write down some
examples of not using contexts, but I think your best bet is to just rewrite
code to take contexts when it can be cancelled. A common mistake that people
make is timing out some operation, usually happening in another goroutine, and
then their program ends up leaking goroutines and crashing because the
abandoned work was never correctly cleaned up. If your API doesn't allow you
to abort, you will have to modify it, because downstream RPC servers come and
go, people press the "stop" button in their browser, TCP connections close,
etc.

------
physicles
In my experience so far, everything beyond mocking the current time is out on
the long tail of tests that are expensive to write and provide little value.
When I've run into a class with a time-based event loop, I isolate the timing
code as well as is reasonable and just test everything else.

Or, create the timer outside and inject its channel. Want to fire the timer?
Just write to the channel.

If you do expect to get a lot of value from testing the event loop, blocking
on individual messages received or not received as in the article is a
reasonable way to de-flakify your tests (in that case, I'd expect the tests to
inject mocks and/or use private interfaces). However, it _is_ a code smell if
any other tests are depending on the details of that event loop.

~~~
cle
It can be useful to directly unit test edge cases in critical concurrent code,
because they are otherwise difficult to test deterministically. But like you
said, I've also found them difficult to write and maintain (re-reading some of
them months later is usually hard). The tests usually end up with 2-3x
channels than the prod code, because I'm forced to inject channels into
various places to control the synchronization. Sometimes though, in a critical
code path, it's worth it.

~~~
jeffbee
Isn't this basic design for testability (TDD, if you like)? If you need some
synchronization facilities to make your unit tests end cleanly, then you also
need those facilities in your general APIs because applications _also_ have
boundary conditions, and pretending that your application exists on an
infinite timeline that neither starts nor ends is naive.

~~~
cle
It's not about ending cleanly, it's about behaving correctly. I would say that
there's nothing "basic" about testing concurrent code--it's always been hard,
no matter what language you use. Exposing your concurrency guts in the API
doesn't make the intrinsic complexity go away, it just shifts it somewhere
else.

------
EdSchouten
I also started using benbjohnson/clock about a year ago, but discovered that
it wasn't a perfect fit:

\- Even though Clock is an interface, Timer is not. This means that if you
want to use that package in combination with gomock, you're out of luck.

\- The Clock interface doesn't provide a wrapper for context.WithTimeout(),
which also depends on the system clock.

\- Nit: The Clock interface also exposes functions like time.After(), which
should in my opinion never be used in production code, as they don't support
efficient cancelation. You should use timers instead.

In the end I resorted to writing my own set of interfaces.

Code: [https://github.com/buildbarn/bb-
storage/tree/master/pkg/cloc...](https://github.com/buildbarn/bb-
storage/tree/master/pkg/clock)

Documentation: [https://pkg.go.dev/github.com/buildbarn/bb-
storage/pkg/clock](https://pkg.go.dev/github.com/buildbarn/bb-
storage/pkg/clock)

~~~
closeparen
>if you want to use that package in combination with gomock, you're out of
luck

In general, libraries _should_ offer structs, and you should declare your own
interfaces with the subset of methods that you require.

This is somewhat messy here because the timer channel is accessed by a field
rather than a method, but you could write a struct that embeds time.Timer and
adds C() as a method. Then both your wrapped time.Timer and your mock/fake
could fulfill your interface.

~~~
EdSchouten
Though that is true, remember that Go doesn’t support co/contravariance on
interface types.

Defining interfaces around existing types works, but quickly falls apart as
soon as methods return concrete types. In those cases you need to write more
complex wrappers, which is often not worth the hassle.

------
rowanseymour
I initially played with passing an instance of a mockable time thing around
everywhere that needed time, but it felt over the top to be passing around so
many things, solely for purposes of testing. So now it's just a thing you mock
at a global level, e.g.

    
    
      defer dates.SetNowSource(dates.DefaultNowSource)
      dates.SetNowSource(dates.NewSequentialNowSource(time.Date(2018, 10, 18, 14, 20, 30, 123456, time.UTC)))
      dates.Now()

~~~
physicles
I've done this too, because passing a mockable time everywhere would just
clutter the code, and this is an instance where a package-level global isn't
going to bite you in the ass -- in prod anyway (in tests you might get your
wires crossed, but seen another way, a global ensures all parts of your
program think it's the same time).

Difference in mine is that dates.SetNowSource returns a reset function that
you can defer, like with context.WithCancel().

------
echlebek
I've maintained a few codebases that relied directly on stdlib time in ways
that were hard to write tests for. Sometimes the code works reliably, but
tests are flaky, due to the author using time.Sleep in tests, or something
else to paper over hard-to-test functions.

I'd say in a lot of, or most circumstances it's best to just bite the bullet
and refactor the mess into something more maintainable.

But there are exceptions to every rule, and let's face it, sometimes it's just
too expensive.

Well what you can often do in these circumstances is just mock out the whole
damn time package. That's what I did when faced with a similar situation and I
was pleasantly surprised with the results.

I wrote
[https://github.com/echlebek/timeproxy](https://github.com/echlebek/timeproxy)
to be a drop-in import for stdlib time. (Modulo any drift in stdlib since it
was written)

I wrote [https://github.com/echlebek/crock](https://github.com/echlebek/crock)
as a fake time implementation that I can exercise complete control over. It
lets me control tickers, timers, time.Now, and all that however I like.

Since timeproxy just dispatches all calls to stdlib by default, I feel
confident using it in production.

crock has some bugs, but they usually only show up in pathological cases, and
you can easily prove that its code path will only get executed in tests, by
ensuring that it only gets imported in _test.go files.

So far to my knowledge I'm the only user of these libraries, although I have
employed them in a large open source application. Really, the libraries are
less important than concept, which I learned from a Go author's slide deck on
mocking out the os library years ago.

------
jonathanoliver
There are two ways I've done this.

1\. Pass in a value called `now func() time.Time` into the constructor
function of my struct. This allows me to call `inst.now()` on a particular
instance of my structure.

2\. Have a global field in a package: `var Now func() time.Time = time.Now`
This allows `mypkg.Now()`

All of this avoids taking yet another external package dependency with only a
couple of lines of code.

~~~
dimonomid
The yet another external dependency is way more than Now() though. Mocking
just Now() is trivial, but most of the time it's by far not the only function
which I personally use from the time package.

~~~
jonathanoliver
Correct. In my use case, I just need a simple replacement for time.Now(). If I
needed more (time.After, for example), there's a point that bringing in an
external dependency makes sense.

------
cmwelsh
I am using java.time.Clock class in Java - really similar to what’s mentioned
for Go in the article! It works great and enables us to write reliable,
comprehensive, reproduce-able unit tests.

~~~
oftenwrong
Same here. Any code that references the current time gets it from a Clock
parameter.

------
jniedrauer
I recently ran into a situation where I had to test some complex scheduling
logic for edge case handling (DST, leap year, new year, etc). Most of the
actual time comparisons were offloaded to a database engine, so I was able to
mock various scenarios with libfaketime. It was definitely a pain to not be
able to mock time in Go using libfaketime though. It's a really rare
requirement, but when it does pop up, it's hard to do without.

------
ramoz
Makes me wonder how simulating time could work? Are there any good reads on
how you'd model an environment where variable time intervals are critical
factors in the simulation?

~~~
jrockway
There is nothing deep going on here. People inject fake clocks so they can
write a test like assertRow(got, row{creationTime: time.Date(2020, 5, 25, 0,
0, 0, 0)}) and have the test always pass. (Or cases like "on Sunday, do X" and
you want your unit tests to work on Sunday as well as other days.)
Additionally, you sometimes want shorter timeouts against your fake backend,
so that when you make an error it takes milliseconds for your test to fail
instead of the longer amount of time you'd prefer in production.

You don't have to use globals for any of this, but people like doing it, and
then have a bunch of workarounds to make the tests work. If you don't want
workarounds, your API can be "func CreateRow(..., now time.Time)" or "func
DoSomethingWithTimeout(timeout time.Duration)" and just pass the correct
values in. (Don't do the timeout one, though, just use a context.)

------
llimllib
Mocking the time module is not very hard, and I recommend writing your own
custom mock that fits exactly what you need unless one of the available
libraries does something particularly fancy that you need

~~~
jeffbee
It's surprisingly tricky, I wouldn't jump to conclusions. It would depend a
great deal on how much fidelity you require to Go's undocumented behavior. For
example did you know that the Go runtime (as of today) will not initiate any
AfterFunc calls until the goroutine that called AfterFunc yields? Is your
application silently reliant on this undocumented synchronization?

~~~
llimllib
Most people probably won't need to figure that out, and if they did then that
would count as the "something fancy" I mentioned.

I'm not jumping to conclusions, I have my own mock for time that I wrote in an
afternoon which has survived several years on a big golang app

------
loopz
If you're using systemtime for anything else than dumping into logs as
timestamps, you're doing it wrong.

Mocking system clocks for testing just confirms that truth. If you can't think
of an alternative to using some variation of systemtime function for business
operational rules, I'm not really sure what you're pushing to production.

~~~
TheDong
Go doesn't differentiate the program's monotonic time from systemtime in terms
of the package interface given.

time.Now in go returns both the system time (i.e. if you do '.Seconds' or
'String' on that time, you get a wall clock time), but also returns a
monotonic time (if you subtract or compare two times, it uses monotonic time).

Go's time package isn't just systemtime, and there's really not an alternative
to it in the language. You're arguing against a strawman.

~~~
loopz
I don't think you got my point though. If you're using timestamps for anything
else than dumping into a logfile (or purely for displaying/cosmetic purposes),
you're doing it wrong. Somewhere along the line, you could end up with a nasty
surprise. Not thinking of clock drift or any kind of seasonal adjustments
here.

Maybe you're using time-package right and avoiding the above, maybe not.
Timestamp usage is best to avoid for anything business critical.

~~~
TheDong
We're misunderstanding each other then.

I assumed this package about mocking time was _not_ about mocking timestamps
but program behavior. For example, if I have a loop that says "every 20
seconds, do X", and I want to test that, I now have to mock time.

That's not about timestamps, that's about comparing monotonic clocks for "20s
have elapsed".

The go time package doesn't just give you a timestamp to do something with, it
also lets you compare times. That was my point.

It sounds like your original comment was arguing that mocking time isn't
useful, so I assumed you meant all uses of time.

~~~
loopz
Fair enough. If you want to mock time differentials like that, I see that one
could use something to mock that. However, what you probably ideally would
want to mock are the order of events, and not certain language-specific
stdlib-calls. Though would depend how explicit you want to define your domain.

