Skip to main content

Jest 27: New Defaults for Jest, 2021 edition ⏩

· 7 min read

In the Jest 26 blog post about a year ago, we announced that after two major releases with few breaking changes, Jest 27 will flip some switches to set better defaults for projects that are new or can migrate smoothly. This gives us the opportunity to remove some packages from the default distribution of Jest 28 and publish them as separately installable and pluggable modules instead. Everyone on the new defaults can benefit from a smaller install size, while people needing these packages can still install them separately.

With the first major change of defaults since the New Defaults for Jest that came with the seminal version 15, Jest 27 is now here, to keep Jest fast, lean, and relevant in the future. We will explain those changes of defaults and other notable breaking changes in this post, but first, let's get into some exciting new features!

Feature updates

Firstly, the interactive mode you may know from reviewing and updating failed snapshots can now also be used to step through failed tests one at a time. Credit goes to first-time contributor @NullDivision for implementing this feature!

Interactive failed test run

Speaking of snapshots, one of the more exciting features we've shipped in recent years are Inline Snapshots, which landed in a minor release of Jest 23 almost three years ago. However, they came with the restriction that projects wanting to utilize them must be using Prettier to format their code, because that's what Jest would use to make sure the file it writes the snapshots into remains properly formatted.
And so for most of these years, we've had a pull request in the pipeline to eliminate this restriction and allow using Inline Snapshots without Prettier. It has amassed well above a hundred comments, not even taking into account PRs split out from it and landed first, and even changed owner once after the initial submission by another first-time contributor, @mmkal under the hilarious working title 'Uglier Inline Snapshots'. With the stellar rise of Prettier in recent times, this improvement is now maybe less needed than back in 2018, but still, we know that feeling of getting into a project that does not use Prettier, and suddenly not being able to use inline snapshots anymore. Nevermore!

The main reason why it took us so long to land this was, somewhat surprisingly, an out of memory error on our build pipeline. It turns out that the dependencies we load for each test file to perform the parsing, snapshot insertion, and printing do incur a significant time and memory overhead.
So with some tricks, we've speed up the initialization per test file by roughly 70% compared to Jest 26. Note that you will almost certainly not see this big of a performance improvement on your project—you would need a lot of test files that each run very quickly to best notice this, and the overhead when using a JSDOM environment dwarfs any such improvement.

In other news, the native ESM support is progressing, but some major complexities, for instance around mocking, are still ahead of us, and we continue to observe the migration to ESM as a huge ecosystem effort, where Node and a lot of crucial tools and packages all have to rely on each other to deliver an overall compelling experience.
ESM support for plugging modules into Jest is more advanced—custom runners, reporters, watch plugins, and many other modules can already be loaded as ES modules.

We've also merged a PR to be able to deal with test files symlinked into the test directory, a feature much wanted by users of Bazel.

Another PR enabled transforms to be asynchronous, a requirement to support transpilation through tools such as esbuild, Snowpack, and Vite effectively.

Flipping defaults

Up until now, if you were using Jest in its default configuration, you were—perhaps unknowingly—running some code forked many years ago from the test runner Jasmine 2.0 that provides test framework functions such as describe, it, and beforeEach. In 2017, Aaron Abramov initially wrote a replacement for the Jasmine code called jest-circus, meant to improve error messages, maintainability, and extensibility.
After years of large-scale use at Facebook and of course in Jest itself, as well as recent adoption in create-react-app, we are now confident that jest-circus is highly compatible with jest-jasmine2 and should work in most environments with little to no migration work. There may be minor differences in execution order and strictness, but we expect no major upgrade difficulties other than for code relying on Jasmine-specific APIs such as jasmine.getEnv(). If you rely extensively on such APIs, you can opt back in to the Jasmine-based test runner by configuring "testRunner": "jest-jasmine2".

Running tests in a JSDOM environment incurs a significant performance overhead. Because this was the default behavior of Jest unless otherwise configured up until now, users who are writing Node apps, for example, may not even know they are given an expensive DOM environment that they do not even need.
For this reason, we are changing the default test environment from "jsdom" to "node". If you are affected by this change because you use DOM APIs and do not have the test environment explicitly configured, you should be receiving an error when e.g. document is accessed, and you can configure "testEnvironment": "jsdom" or use per-file environment configuration to resolve this.
For mixed projects where some tests require a DOM environment but others don't, we recommend using the fast "node" environment by default and declaring exactly those tests that need the DOM using docblocks.
In the next major, we plan to also eliminate jest-jasmine2 and jest-environment-jsdom from the Jest dependency tree and require them to be installed explicitly, so that many users can profit from a smaller install size with less clutter that they don't need.

Another default that we are changing affects Fake Timers aka Timer Mocks. We introduced an opt-in "modern" implementation of Fake Timers in Jest 26 accessed transparently through the same API, but with much more comprehensive mocking, such as for Date and queueMicrotask.
This modern fake timers implementation will now be the default. If you are among the unlucky few who are affected by the subtle implementation differences too heavily to migrate, you can get back the old implementation using jest.useFakeTimers("legacy") or, if you are enabling fake timers globally via configuration, "timers": "legacy".

Features coming with breaking changes

We introduced a few more small breaking changes to help you avoid mistakes by disallowing some things that can easily happen unintentionally:

  • The same done test callback may not be called more than once,
  • calling done and returning a Promise may not be combined,
  • a describe block must not return anything,

and we made some TypeScript types stricter.

Modules used in the following configuration options are now transformed like the rest of your code, which may be breaking if you relied on them being loaded as-is:

  • testEnvironment
  • runner
  • testRunner
  • snapshotResolver

Miscellaneous breaking changes

We removed some long-deprecated functions:

  • jest.addMatchers (use expect.extend instead)
  • jest.resetModuleRegistry (use jest.resetModules instead)
  • jest.runTimersToTime (use jest.advanceTimersByTime instead)

A lot of Jest's packages have been migrated to use ESM-style exports (although they are still shipped as CommonJS), so if you consume e.g. pretty-format directly, you may need to adjust your import to a default import.

We dropped support for Node 13—but Jest always supports the Current and all LTS Node versions, and Jest 27 continues to support Node 10, which only recently became unmaintained.

As always, the full changelog and list of breaking changes can be viewed here.

Finally, we'd like to thank the community for once again awarding Jest a sky-high satisfaction rating of 96% in the State of JS 2020 survey! Stay safe everyone, and we hope you continue to enjoy using Jest in the years and versions to come! 🃏