Mar 08, 2020

Configuring Jest Snapshot Resolvers

Snapshot Testing is a powerful method to build assertions that hold up throughout the development lifecycle. Chances are, that you're already snapshotting parts of your systems, be it network data or results of pure functions. In the Node.js ecosystem, Jest is a popular testing framework, allowing to set up powerful testing infrastructure in little time.

By default, Jest stores snapshots in a directory next to the test file it's running. So, if you're running the following directory structure

.
├── User.spec.js
├── Post.spec.js
└── Payment.spec.js

Jest will create a __snapshots__ directory containing the following files next to it

.
├── __snapshots__
│   ├── User.spec.js.snap
|   ├── Post.spec.js.snap
│   └── Payment.spec.js.snap
├── User.spec.js
├── Post.spec.js
└── Payment.spec.js

Now, let's say you're running a TypeScript-based setup and have built your tests before, your snapshots would end up in the build directory, instead of sitting close to the test files they're based on. Surely, we can do better.

Luckily, Jest provides the snapshotResolver configuration option, which allows to provide a custom Node.js module exposing utility functions to resolve a snapshot file path based on a test file path and the snapshot extension, and vice-versa. We'll leverage this to store our snapshots in a custom location.

// Resolve snapshot file path given test file path
// and snapshot extension to be used (defaults to .snap)
// turns dist/src/__test__/Post.spec.js into
// src/__test__/__snapshot__/Post.spec.ts.snap
function resolveSnapshotPath(testPath, snapshotExtension) {
  // Get source path of test file (assuming the build
  // output is stored in dist/ and a TypeScript file)
  const testSourcePath = testPath.replace('dist/', '').replace('.js', '.ts');

  // Get directory of test source file
  const testDirectory = path.dirname(testSourcePath);

  // Get file name of test source file
  const testFilename = path.basename(testSourcePath);

  // Construct file path for snapshot, saved next to original test source file
  return `${testDirectory}/__snapshots__/${testFilename}${snapshotExtension}`;
}

This function receives the test file's relative path to the root directory, for example, dist/src/__test__/Post.spec.js, returning a transformed file path for the snapshot created by the tests the file contains. On to the next function!

// Resolve test file path given snapshot file path and extension
// of snapshot files (defaults to .snap)
// turns src/__test__/__snapshot__/Post.spec.ts.snap into
// dist/src/__test__/Post.spec.js
function resolveTestPath(snapshotFilePath, snapshotExtension) {
  // Transform snapshot file path
  const testSourceFile = snapshotFilePath
    // Remove __snapshot__ directory
    .replace(`/__snapshots__`, '')
    // Convert .ts (TypeScript) to .js to reach built test file
    .replace('.ts', '.js')
    // Remove snapshot extension so we end up with .js
    .replace(snapshotExtension, '');

  // Return test file path in dist directory
  return `dist/${testSourceFile}`;
}

This does exactly the opposite compared to the first function. We'll receive a snapshot file path and return a transformed version to locate the test file based on it. Now let's combine the pieces!

const path = require('path');

// <Functions From Above>

module.exports = {
  resolveSnapshotPath,
  resolveTestPath,

  // This is an example test file path to guarantee internal consistency
  // Will be used to check if the functions above work properly
  testPathForConsistencyCheck: 'dist/src/__test__/Post.spec.js'
};

Those two functions are everything we need to customize where exactly Jest stores our snapshots. It's really powerful and we could be even more creative, but that should do it for now. The last thing is to adjust our jest.config.js and add our snapshot resolver

module.exports = {
  // ...
  snapshotResolver: '<rootDir>/snapshotResolver.js'
};

Now we can happily add the snapshots to version control and stay ahead of breaking changes.


I hope you enjoyed this short guide, maybe this will come in handy! If you've got any questions, suggestions or other feedback, don't hesitate to reach out on Twitter or by mail.