Nona Blog

Writing unit tests for old code can be a pleasure

How to write new tests for old code in Javascript with jest mocks

I have often seen projects deteriorate due to a lack of (or no) testing and most of the time, the educated answer for how to solve this problem is:

Just don’t allow it to happen in the first place

Although true, this is not very useful when we are stuck with what we believe to be untestable code.

There are two main underlying reasons that create this scenario:

  1. There is a 3rd party or native library that has been tightly coupled to the logic of the code which makes it difficult to test.
  2. There is a utility class/library that was written years ago which nobody understands or wants to touch.

So what happens when we need to write and test new code that touches some “untestable” code without perpetuating the problem?

Lets take a look at an example

// car.js

const DbUtil = require('./dbUtil')

class Car {
  constructor(id) {
    this.id = id
    this.brand = DbUtil.getBrandById(id)
  }

getFormattedBrandName() {
    return (
      this.brand.charAt(0).toUpperCase() + this.brand.substring(1)
    )
  }
}

module.exports = Car
// car.test.js

const Car = require('./car')

describe('getFormattedBrandName', () => {
  test('should format the name of a given car brand', () => {
    // ford has the ID of 1
    const mycar = new Car(1)
    expect(mycar.getFormattedBrandName()).toEqual('Ford')
  })
})

When we run this test we will inevitably be presented with an error of death which points to the dreaded DBUtils library, panic ensues, the end is neigh!

The error usually looks something like this:

TypeError: DbUtil.getBrandById is not a function

Mocks to the rescue!

With a small tweak to our test file we can fix this error as well as actually test what we need to test – which is whether or not getFormattedBrandName works as expected:

jest.mock('./dbUtil', () => {
  return {
    getBrandById: () => 'ford',
  }
})

Let’s break this down a little:

In this example we are assuming our DbUtil class is in a file with a relative project path of/.dbUtil , but this could easily be a node module, which in that instance would be something like this:

// car.test.js

jest.mock('some-db-util-lib', () => {
// package.json

...
"dependencies": {
"some-db-util-lib": "1.0.0"
...

9/10 times we will want to mock 3rd party libraries in our tests, especially if they have behaviour which has unpredictable results or touches any underlying hardware components.

Going back to our example, in this instance we use a fake version of of DbUtil class which only contains the methods that we actually want to call within this particular test (getBrandById)

  return {
    getBrandById: () => 'ford',
  }

All of the code for this example can be found here — https://github.com/richlloydmiles/medium-code-cleanup

Conclusion

This is already a heck of a lot better than having no tests for our code, and although we have not actually written any tests for our “old black box” code yet this is a good place to start. In fact this is the preferred method to start chipping away at refactoring and writing tests for the old “black box” code – now that you know how to isolate and test the parts you do understand.

If you begin to think of your code as smaller pieces of functionality that can be tested in isolation  with the help of mocking— even if they exist in an environment tightly coupled with old code —  refactoring and testing becomes a much more pleasant experience.

Ideally, you will begin to write and refactor your code in a way where it becomes obvious as to what you’ve actually changed and need to test, as opposed to what is just boilerplate or 3rd party code that should be mocked.

Richard

Richard

Add comment