Singular is available!

Start Reading

Testing with Jest, Promises, Mocks, and Third-Party Libraries

As javascript development has largely become a jigsaw puzzle of trending buzzwords, I’m going to write today about a technique for employing Jest and Dependency Injection to test a Node.js application with asynchronous promise-driven third-party libraries. Sounds fun, right?

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.

Background

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:

export function getHistory (options, (err, response) => {}) {}

So this function wants you to provide a callback for processing the result. Great.

Let’s say you tried to use it like so:

import tpl from 'tpl'

function processHistory (ctx) {
  let result = tpl.getHistory({}, (err, response) => {
    if (err) {
      return err
    } else {
      return response
    }
  })
  // do something with result?
}

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:

import tpl from 'tpl'

function promisedFunction (ctx) {
  return new Promise((resolve, reject) => {
    tpl.getHistory({}, (err, response) => {
      if (err) {
        reject(err)
      } else {
        resolve(response)
      }
    })
  })
}

// now we can do this instead...
function usesTheOtherFunction(ctx) {
  promisedFunction(ctx)
  .then(result => {
    // do something with the result
  })
  .catch(err => {
    // do something with the rejection
  })
}

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.

import tpl from 'tpl'

function promisedFunction (ctx) {
  return new Promise((resolve, reject) => {
    ctx.tpl.getHistory({}, (err, response) => {
      if (err) {
        reject(err)
      } else {
        resolve(response)
      }
    })
  })
}

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:

function generateContext () {
  return {
    tpl: {}
  }
}

Now when I inject that into my promisedFunction above, I can substitute the implementation with a mock trivially…like:

ctx = generateContext()
ctx.tpl.getHistory = jest.fn().mockImplementation(cb => {
  cb(false, { history: [1, 2, 3]})
  })
)

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.

describe('an example with promises', () => {
  var ctx
  beforeEach(() => {
    ctx = {
      tpl: {
        getHistory: jest.fn().mockImplementation(cb => {
          cb(false, {foo: 'bar'})
        })
      }
    }
  })

  test('meaningful message', () => {
    return promisedFunction(ctx)
      .then(result => {
        expect(result.foo).toBe('bar')
      })
      .catch(err => expect(err).toBe())
  })
})

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 expect(...). It makes sense why you need to do this, I just didn’t have my head in javascript mode when I ran into this.

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 beforeEach.

Q&A

Wherein I try to anticipate a few of your questions.

Why Jest?

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.

What about resolves in Jest 20.0.0?

Jest 20 wasn’t available at the time, but it will be their new way to handle promises.

What about __mocks__?

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.

Fin

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.

Published under programming, twitter

Related Posts

One of the things that I’ve missed about Go is the incredibly convenient gofmt tool. Through static analysis of your code (that means it converts it into an AST, an intermediary representation) gofmt can take your messy code and rewrite it in a standardized format. It completely eliminates a whole category of code “aesthetics” arguments.