Testing with Jest, Promises, Mocks, and Third-Party Libraries
The choice of all of these tools is arbitrary and the following could be accomplished in many different ways, so don’t take this as authoritative or even exemplary. It’s simply useful.
There’s a few concepts we need to get a handle on in order to be able to reach our goal. Let’s run through these quickly.
Concept 1: Promises
Suppose you need to get data from a third-party. You’ve been provided a library that talks to the service and has a function signature like:
So this function wants you to provide a callback for processing the result. Great.
Let’s say you tried to use it like so:
Sadly, this will not work, as
getHistory will immediately return something other than what you want, and execution will carry on.
While you could shove all your work inside the callback, another option is to employ promises to hoist the control flow back up outside of the function. Maybe that’s a taboo way of describing it, but the idea is to avoid bracket-hell with promises. Like so:
Now we can build our applications as if operations were sequential. Hurray.
Concept 2: Mocking
Get it, Mocking? Jest? … Get it?
So Jest does many nifty things, but I’m primarily using it for its mocking capabilities. There are formal definitions, I’m sure, but I think of mocking as replacing an actual implementation with a temporary one.
Let’s go back to that function that calls the network, talks to a third-party site, and gets data back.
When I’m testing, I don’t want to do that. One way I can handle that is by mocking it out. For reasons derived from the actual project I’m taking these snippets from, I have a
ctx object that I pass around like an accumulator.
If I make one tiny change, mocking becomes really easy.
As you can see, I’ve moved the third party library from a global to something I can call off my context object. This allows me to control whether I’m using a real implementation (in production) or a mocked one (in test). You could just as easily pass two arguments in to your function. Doesn’t matter to me.
In testing then, I build my context like so:
Now when I inject that into my
promisedFunction above, I can substitute the implementation with a mock trivially…like:
Just like that, no network calls, and you can directly control the output (and check the inputs) of the third party library.
Testing Promises with Jest
Ok, onto the actual testing, now that we have the preliminaries out of the way.
Gotcha 1 - return
One of the tricks with this approach, is you have to
return promisedFunction(...) in your test. Without the
return, nothing happens. That’s different than how you’d normally write a test, with a simple function call and then an
Gotcha 2 - catch
Another downside of this approach is the ubiquitous
catches make tracking down the actual source of a bug very tedious. They catch everything, so you’ll get
can't call split on undefined and you’ll have no idea where the call site is. I’m still working on a solution for this.
Gotcha 3 - context
If you’re not careful, you can carry state over accidentally between tests. Originally I had a
fixtures import that I was attaching mocks to, but I quickly realized that was persisting state. Instead, I implemented my “fixtures” as functions which return state and changed it for the whole thing to be rebuilt in every
Wherein I try to anticipate a few of your questions.
As opposed to other test runners, this is the one that my coworkers want to use. I’m not sure why.
What about jest.enableAutomock()
I saw that in the docs, but I figured I’d rather take a route where I could see the whole implementation. Also, in case this feature of Jest changes again.
resolves in Jest 20.0.0?
Jest 20 wasn’t available at the time, but it will be their new way to handle promises.
It looks like Jest has support for overiding specific functions with mocks that you can load via
jest.mock('..whatever') but it wasn’t really intuitive how that would work for a third party function. Rather than playing with
nextTick, I employed my DI approach. Clearly, their approach would work nicely for mocking your own code whilst doing unit testing on a larger project. Though to me, it would seem to imply needing to break everything into very tiny blocks (since you can’t selectively turn on/off the mock), which is tedious at times.
Anyway, there’s one way to test a pretty typical scenario with a third-party library with asynchronous functions in your own promise oriented application. Not the only way, probably not the “blessed” way, but it works.