---
title: 'Jest 30: Faster, Leaner, Better'
authors: [MillerSvt, cpojer]
---

Today we are happy to announce the release of Jest 30. This release features a substantial number of changes, fixes, and improvements. While it is one of the largest major releases of Jest ever, we admit that three years for a major release is too long. In the future, we are aiming to make more frequent major releases to keep Jest great for the next decade.

If you want to skip all the news and just get going, run `npm install jest@^30.0.0` and follow the migration guide: [Upgrading from Jest 29 to 30](https://jestjs.io/docs/upgrading-to-jest30).

## What’s New?

Jest 30 is noticeably faster, uses less memory, and comes with tons of new features. First, let’s take a look at the breaking changes:

## Breaking Changes

- Jest 30 drops support for Node 14, 16, 19, and 21.
- `jest-environment-jsdom` was upgraded from jsdom 21 to 26.
- The minimum compatible TypeScript version is now 5.4.
- Various `expect` aliases were removed. [`eslint-plugin-jest` has an autofixer](https://github.com/jest-community/eslint-plugin-jest/blob/HEAD/docs/rules/no-alias-methods.md) which you can run to automatically upgrade your codebase.
- Non-enumerable object properties are now excluded from object matchers such as `toEqual` by default.
- Jest now supports `.mts` and `.cts` files by default.
- `--testPathPattern` was renamed to `--testPathPatterns`.
- Jest now properly handles promises that are first rejected and then later caught to avoid false positive test failures.
- We made various improvements to Jest’s printing of snapshots which might require you to update snapshots. Google deprecated `goo.gl` links which we were using in snapshots. We don’t like it either, but you’ll have to update all your snapshots.
- Jest itself is now bundled into a single file per package. This improves performance, but might break if you built tools that reach into Jest's internals.

These are just some of the highlights. The full list of breaking changes can be found in the [CHANGELOG](https://github.com/jestjs/jest/blame/main/CHANGELOG.md) and the [Jest 30 migration guide](https://jestjs.io/docs/upgrading-to-jest30).

## Performance & Memory Improvements

Jest 30 delivers real-world performance gains thanks to many optimizations, especially related to module resolution, memory usage, and test isolation. By relying on the new [unrs-resolver](https://github.com/unrs/unrs-resolver), module resolution in Jest became more feature-rich, standards-compliant, and faster. Thanks to [@JounQin](https://github.com/JounQin) for the migration. Depending on your project, you may see significantly faster test runs and reduced memory consumption. For example, one large TypeScript app with a client and server observed 37% faster test runs and 77% lower memory usage in one part of their codebase:

|                  | Jest 29             | Jest 30                |
| ---------------- | ------------------- | ---------------------- |
| **Server tests** | ~1350s / 7.8 GB max | **~850s / 1.8 GB max** |
| **Client tests** | ~49s / 1.0 GB max   | **~44s / 0.8 GB max**  |

Jest is fast, but due to Jest's test isolation, slow user code often exacerbates performance issues and leads to slow test runs. When tests leave behind open handles like unclosed timers or connections to other services, it can cause Jest to hang or slow down. Jest 30 has gotten better at detecting and reporting these issues, which helps you identify and fix slow or problematic tests more easily. For example, tests at [Happo](https://happo.io/) were sped up by 50% from 14 minutes down to 9 minutes by cleaning up open handles and upgrading to Jest 30.

If you are using files that consolidate the exports of multiple modules into a single file (i.e. "barrel files"), we recommend using tools such as [`babel-jest-boost`](https://github.com/gtsop/babel-jest-boost), [`babel-plugin-transform-barrels`](https://github.com/FogelAI/babel-plugin-transform-barrels) or [`no-barrel-file`](https://github.com/Nergie/no-barrel-file) to avoid loading large swaths of application code for each test file. This can lead to performance improvements of up to 100x.

### Globals cleanup between test files

Jest achieves test isolation between files by running each test in a separate [VM context](https://nodejs.org/api/vm.html#vm-executing-javascript), giving each file a fresh global environment. However, if your code does not clean up globals after each test file, it can lead to memory leaks across Jest and slow down your test runs. Jest 30 introduces a new feature that notifies you about globals that are not correctly cleaned up after a test run.

In the future, Jest will automatically clean up globals after each test run. If you don't get any warnings about uncleaned globals with Jest 30, you can already set the globals cleanup mode to "on" now to enable this feature fully, and benefit from major memory savings and performance improvements:

```javascript
export default {
  testEnvironmentOptions: {
    globalsCleanup: 'on',
  },
};
```

The default in Jest is `globalsCleanup: 'soft'`. To disable this feature you can set it to `off`. If you need to protect specific global objects from being cleaned up -- for example, shared utilities or caches -- you can mark them as protected using `jest-util`:

```ts
import {protectProperties} from 'jest-util';

protectProperties(globalThis['my-property']);
```

Thanks to [@eyalroth](https://github.com/eyalroth) for implementing this feature!

## New Features

### Improved ECMAScript Module & TypeScript Support

Support for `import.meta.*` and `file://` was added [when using native ESM with Jest](https://jestjs.io/docs/ecmascript-modules). In addition, you can now write your Jest config files in TypeScript, and `.mts` and `.cts` files are natively supported without requiring extra configuration. If you are using Node’s native TypeScript type stripping feature, we no longer load the TypeScript transformer to strip types, leading to faster test runs.

### Spies and the `using` keyword

You can now use [JavaScript’s new explicit resource management syntax (`using`)](https://v8.dev/features/explicit-resource-management) with Jest spies. If your environment supports it, writing `using jest.spyOn(obj, 'method')` will automatically restore the spy when the block ends, so you don’t have to manually clean up.

```typescript
test('logs a warning', () => {
  using spy = jest.spyOn(console, 'warn');
  doSomeThingWarnWorthy();
  expect(spy).toHaveBeenCalled();
});
```

[Documentation](/docs/jest-object#spied-methods-and-the-using-keyword)

### `expect.arrayOf`

Jest 30 introduces a new [asymmetric matcher](https://jestjs.io/docs/expect#asymmetric-matchers), `expect.arrayOf`, which lets you validate every element of an array against a condition or type. For instance, you can expect an array of numbers ensuring all items are numbers:

```javascript
expect(someArray).toEqual(expect.arrayOf(expect.any(Number)));
```

[Documentation](/docs/expect#expectarrayofvalue)

### New `test.each` placeholder: `%$`

If you use data-driven tests with `test.each`, you can now include a special placeholder `%$` in your test titles to inject the **number of the test case**. For example:

```javascript
test.each(cases)('Case %$ works as expected', () => {});
```

will replace `%$` with the test’s sequence number.

[Documentation](/docs/api#testeachtablename-fn-timeout)

### `jest.advanceTimersToNextFrame()`

[`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers) was upgraded to v13, adding `jest.advanceTimersToNextFrame()`. This new function allows you to advance all pending `requestAnimationFrame` callbacks to the next frame boundary, making it easier to test animations or code that relies on `requestAnimationFrame` without having to guess millisecond durations.

[Documentation](/docs/jest-object#jestadvancetimerstonextframe)

### Configurable test retries

Jest 30 enhances `jest.retryTimes()` with new options that give you control over how retries are handled. You can specify a delay or immediately retry a failed test instead of waiting until the entire test suite finishes:

```js
// Retry failed tests up to 3 times, waiting 1 second between attempts:
jest.retryTimes(3, {waitBeforeRetry: 1000});

// Immediately retry without waiting for other tests to finish:
jest.retryTimes(3, {retryImmediately: true});
```

[Documentation](/docs/jest-object#jestretrytimesnumretries-options)

### `jest.unstable_unmockModule()`

Jest 30 adds new experimental `jest.unstable_unmockModule()` API for finer control when unmocking modules (especially when using native ESM).

[Documentation](/docs/ecmascript-modules#module-unmocking-in-esm)

### `jest.onGenerateMock(callback)`

A new `onGenerateMock` method was added. It registers a callback function that is invoked whenever Jest generates a mock for a module. This callback allows you to modify a mock before it is returned to your test environment:

```javascript
jest.onGenerateMock((modulePath, moduleMock) => {
  if (modulePath.includes('Database')) {
    moduleMock.connect = jest.fn().mockImplementation(() => {
      console.log('Connected to mock DB');
    });
  }
  return moduleMock;
});
```

[Documentation](/docs/jest-object#jestongeneratemockcb)

## Other Improvements

### Custom object serialization

Jest’s matcher utilities now support defining a static `SERIALIZABLE_PROPERTIES` on custom objects. This allows you to control which properties of a custom object are included in snapshots and error messages, making the output more focused and relevant.

[Documentation](https://jestjs.io/docs/next/expect#serializable_properties)

### Asynchronous setup support

Test files listed in `setupFilesAfterEnv` can now export an async function or use top-level `await` similar to `setupFiles`.

### And so much more…

Check out the full [CHANGELOG](https://github.com/jestjs/jest/blame/main/CHANGELOG.md) for all changes, improvements and new features.

### Known Issues

[jsdom has made changes to become more spec compliant](https://github.com/jsdom/jsdom/issues/3492). This might break some use cases, most notably mocking `window.location` in tests. Jest now ships with `@jest/environment-jsdom-abstract` to make it easier for you to compose your own custom test environment based on jsdom. If you are just looking to patch `jsdom`, you can apply [this `jsdom` patch to your project](https://gist.github.com/cpojer/e66f9a082021a82230f2595a6027f161). In the future, we may look into providing an alternative to `jsdom` that is better suited for testing.

## What's Next

Jest has been the most popular JavaScript testing framework for a decade. It is used by millions of developers, supporting a wide range of projects from small libraries to the largest codebases in the world. Jest has constantly been improved over time, and as with all long-lasting software projects used in the real world, we accumulated technical debt. We support some features that only few people or companies use, and we have kept breaking changes to a minimum to avoid disrupting users. Some features should be made possible by Jest, but not as part of the core framework. Other features promote testing the wrong things, and should maybe not be part of Jest at all. In terms of Jest's team, a few of us moved on over time which led to slower progress and fewer releases. Here is how we are going to address these issues going forward:

- **Performance / Technical Debt:** Slim Jest down into a leaner, more performant core. Remove features that are not used by the majority of users, and focus on what makes Jest great.
- **Consistent Release Cycles:** We will aim to be more consistent with our release cycles and deprecation policies.
- **Be Open:** Build everything in the open, and be transparent about our plans. Provide more opportunities to get involved and increase the number of contributors.
- **Be Bold:** As the Jest team, we should be more bold. There are a bunch of things that holds Jest back from what it could be. It's time to make moves.

The great news is that Jest has always been well set up to deliver on these principles, ever since we built the framework as a modular system with clear separation of concerns. Now it's time to execute. _More on all that soon!_

## Thanks

This release wouldn’t have been possible without the hard work of our community. Thank you.

[@SimenB](https://github.com/SimenB), [@mrazauskas](https://github.com/mrazauskas), [@Connormiha](https://github.com/Connormiha), [@liuxingbaoyu](https://github.com/liuxingbaoyu), [@k-rajat19](https://github.com/k-rajat19), [@G-Rath](https://github.com/G-Rath), [@charpeni](https://github.com/charpeni), [@dubzzz](https://github.com/dubzzz), [@stekycz](https://github.com/stekycz), [@yinm](https://github.com/yinm), [@lencioni](https://github.com/lencioni), [@phawxby](https://github.com/phawxby), [@lukeapage](https://github.com/lukeapage), [@robhogan](https://github.com/robhogan), [@fisker](https://github.com/fisker), [@k-rajat19](https://github.com/k-rajat19), [@connectdotz](https://github.com/connectdotz), [@alesmenzel](https://github.com/alesmenzel), [@rickhanlonii](https://github.com/rickhanlonii), [@mbelsky](https://github.com/mbelsky), [@brunocabral88](https://github.com/brunocabral88), [@brandon-leapyear](https://github.com/brandon-leapyear), [@nicolo-ribaudo](https://github.com/nicolo-ribaudo), [@dj-stormtrooper](https://github.com/dj-stormtrooper), [@eryue0220](https://github.com/eryue0220)

A special thanks to everyone who made their first contribution to Jest in this release. Thank you for making Jest better for everyone!

[@eyalroth](https://github.com/eyalroth), [@KhaledElmorsy](https://github.com/KhaledElmorsy), [@mohammednumaan](https://github.com/mohammednumaan), [@bensternthal](https://github.com/bensternthal), [@BondarenkoAlex](https://github.com/BondarenkoAlex), [@phryneas](https://github.com/phryneas), [@jayvdb](https://github.com/jayvdb), [@brandonchinn178](https://github.com/brandonchinn178), [@latin-1](https://github.com/latin-1), [@rmartine-ias](https://github.com/rmartine-ias), [@fa93hws](https://github.com/fa93hws), [@Dunqing](https://github.com/Dunqing), [@gustav0d](https://github.com/gustav0d), [@noritaka1166](https://github.com/noritaka1166), [@andreibereczki](https://github.com/andreibereczki), [@Dreamsorcerer](https://github.com/Dreamsorcerer), [@satanTime](https://github.com/satanTime), [@icholy](https://github.com/icholy), [@ecraig12345](https://github.com/ecraig12345), [@cgm-16](https://github.com/cgm-16), [@sebastiancarlos](https://github.com/sebastiancarlos), [@dancer1325](https://github.com/dancer1325), [@loganrosen](https://github.com/loganrosen), [@zakingslayerv22](https://github.com/zakingslayerv22), [@dev-intj](https://github.com/dev-intj), [@tez3998](https://github.com/tez3998), [@anbnyc](https://github.com/anbnyc), [@pengqiseven](https://github.com/pengqiseven), [@thypon](https://github.com/thypon), [@co63oc](https://github.com/co63oc), [@danielrentz](https://github.com/danielrentz), [@jonasongg](https://github.com/jonasongg), [@andrew-the-drawer](https://github.com/andrew-the-drawer), [@phryneas](https://github.com/phryneas), [@hyperupcall](https://github.com/hyperupcall), [@tonyd33](https://github.com/tonyd33), [@madcapnmckay](https://github.com/madcapnmckay), [@dongwa](https://github.com/dongwa), [@gagan-bhullar-tech](https://github.com/gagan-bhullar-tech), [@ikonst](https://github.com/ikonst), [@ZuBB](https://github.com/ZuBB), [@jzaefferer](https://github.com/jzaefferer), [@brandonnorsworthy](https://github.com/brandonnorsworthy), [@henny1105](https://github.com/henny1105), [@DmitryMakhnev](https://github.com/DmitryMakhnev), [@askoufis](https://github.com/askoufis), [@RahulARanger](https://github.com/RahulARanger), [@Jon-Biz](https://github.com/Jon-Biz), [@fynsta](https://github.com/fynsta), [@KonnorRogers](https://github.com/KonnorRogers), [@BondarenkoAlex](https://github.com/BondarenkoAlex), [@mouadhbb](https://github.com/mouadhbb), [@kemuridama](https://github.com/kemuridama), [@Avi-E-Koenig](https://github.com/Avi-E-Koenig), [@davidroeca](https://github.com/davidroeca), [@akwodkiewicz](https://github.com/akwodkiewicz), [@mukul-turing](https://github.com/mukul-turing), [@dnicolson](https://github.com/dnicolson), [@colinacassidy](https://github.com/colinacassidy), [@ofekm97](https://github.com/ofekm97), [@haze](https://github.com/haze), [@Vadimchesh](https://github.com/Vadimchesh), [@peterdenham](https://github.com/peterdenham), [@ShuZhong](https://github.com/ShuZhong), [@manoraj](https://github.com/manoraj), [@nicolo-ribaudo](https://github.com/nicolo-ribaudo), [@georgekaran](https://github.com/georgekaran), [@MathieuFedrigo](https://github.com/MathieuFedrigo), [@hkdobrev](https://github.com/hkdobrev), [@Germandrummer92](https://github.com/Germandrummer92), [@CheadleCheadle](https://github.com/CheadleCheadle), [@notaphplover](https://github.com/notaphplover), [@danbeam](https://github.com/danbeam), [@arescrimson](https://github.com/arescrimson), [@yepitschunked](https://github.com/yepitschunked), [@JimminiKin](https://github.com/JimminiKin), [@DerTimonius](https://github.com/DerTimonius), [@vkml](https://github.com/vkml), [@ginabethrussell](https://github.com/ginabethrussell), [@jeremiah-snee-openx](https://github.com/jeremiah-snee-openx), [@WillianAgostini](https://github.com/WillianAgostini), [@casey-lentz](https://github.com/casey-lentz), [@faizanu94](https://github.com/faizanu94), [@someone635](https://github.com/someone635), [@rafaelrabelos](https://github.com/rafaelrabelos), [@RayBrokeSomething](https://github.com/RayBrokeSomething), [@DaniAcu](https://github.com/DaniAcu), [@mattkubej](https://github.com/mattkubej), [@tr1ckydev](https://github.com/tr1ckydev), [@shresthasurav](https://github.com/shresthasurav), [@the-ress](https://github.com/the-ress), [@Mutesa-Cedric](https://github.com/Mutesa-Cedric), [@nolddor](https://github.com/nolddor), [@alexreardon](https://github.com/alexreardon), [@Peeja](https://github.com/Peeja), [@verycosy](https://github.com/verycosy), [@mknight-atl](https://github.com/mknight-atl), [@maro1993](https://github.com/maro1993), [@Eric-Tyrrell22](https://github.com/Eric-Tyrrell22)
