Live data from Hacker News

Mocking time and testing event loops in Go

dmitryfrank.com

1–10 of 35 posts

Re: Mocking time and testing event loops in Go

#3
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...

Documentation: https://pkg.go.dev/github.com/buildbarn/bb-storage/pkg/clock

Re: Mocking time and testing event loops in Go

#4
post #2

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.

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

Re: Mocking time and testing event loops in Go

#5
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.

Re: Mocking time and testing event loops in Go

#7

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 t…

Not a mock obviously but in the interest of getting things done on schedule I’d suggest running tests in a container with https://github.com/wolfcw/libfaketime and orchestrating edge cases like leap seconds external to the test suite itself.

Re: Mocking time and testing event loops in Go

#8
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()

Re: Mocking time and testing event loops in Go

#10
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.

Post reply on HN