blog.pshrmn

Testing Source & Build Code with Jest

2019 M04 30

JavaScript tests are typically run against source code. Testing source code lets you know whether or not the code that you are writing passes the tests. What it doesn't do is let you know if the code that you publish passes the tests.

In a perfect world, testing source code would be enough. However, build systems have the potential to introduce bugs, so testing builds can increase your confidence in the code that you are publishing.

An added benefit of testing against published code is that you are only writing tests for the application's public API.

A Test Module

Let's start with a test module that imports a function from the source code.

// tests/add.spec.js
import { add } from "../src/index.js";

describe("add", () => {
  // ...
});

The above test is importing the add function directly from the source. How can we import from the built code instead?

Testing Build Code

The quick fix would be to change the import from "src" to "dist" (or wherever the built code exists).

// tests/add.spec.js
import { add } from "../dist/index.js";

describe("add", () => {
  // ...
});

Now, we can't test against the source code. What if we want to do both? A brute force fix would be to maintain a copy of the tests for each build, but what we really want is a way to run the same tests against different modules.

Jest Configuration

Jest has a moduleNameMapper configuration option, which lets you capture file imports and point them to another file. In theory this is for stubbing resource files, but we can also use it to point to a JavaScript module.

// jest.config.js
module.exports = {
  moduleNameMapper: {
    "^my-package$": "<rootDir>/dist/index.js"
  }
};

The test file can be modified to import from "my-package" instead of from the source code. When the test is run, it will import the add function from "/dist/index.js".

// tests/add.spec.js
import { add } from "my-package";

describe("add", () => {
  // ...
});

By itself, the option only allows us to map an import to some other module, but how can this help us test multiple modules?

Testing Source & Build Code

Instead of hard coding the mapped module, we can use an environment variable to determine which module to use.

For example, we can use the environment variable "TEST_ENV". If it is set to "cjs", we can test against the built CommonJS code. If we wanted to test other builds, we could also specify those. In the default case, we will default to testing the source code.

// jest.config.js
let mappedModule;
switch (process.env.TEST_ENV) {
  case "cjs":
    mappedModule = "<rootDir>/dist/index.js";
    break;
  default:
    mappedModule = "<rootDir>/src/index.js";
}
module.exports = {
  moduleNameMapper: {
    "^my-package$": mappedModule
  }
};

With that, we can run tests against both source code and the application's CommonJS build. (Remember to actually build the code before running tests against it!)

We could run the tests directly from the command line, but let's also modify the package's scripts.

The cross-env package can be used to set environment variables across operating systems.

{
  "scripts": {
    "test": "npm run test:src && npm run test:cjs",
    "test:src: "jest",
    "test:cjs": "cross-env TEST_ENV=cjs jest"
  }
}
# test source
npm run test:src

# test cjs build
npm run test:cjs

# test both
npm test