|
| 1 | +--- |
| 2 | +description: 'Rstest 0.11 is a transitional release on the way to 1.0: it breaks and settles the test options, mock, pool, and shard APIs, and also makes V8 coverage faster and aligns fake timers with the latest @sinonjs/fake-timers.' |
| 3 | +date: 2026-07-06 16:00:00 |
| 4 | +sidebar: false |
| 5 | +authors: |
| 6 | + - name: 9aoy |
| 7 | + avatar: 'https://github.com/9aoy.png' |
| 8 | + github: '9aoy' |
| 9 | + - name: fi3ework |
| 10 | + avatar: 'https://github.com/fi3ework.png' |
| 11 | + github: 'fi3ework' |
| 12 | +--- |
| 13 | + |
| 14 | +import { BlogAuthors } from '@rstack-dev/doc-ui/blog-authors'; |
| 15 | + |
| 16 | +# Announcing Rstest 0.11 |
| 17 | + |
| 18 | +_July 6, 2026_ |
| 19 | + |
| 20 | +<BlogAuthors /> |
| 21 | + |
| 22 | +<img |
| 23 | + src="https://assets.rspack.rs/rstest/rstest-banner-v0-11.png" |
| 24 | + alt="Rstest 0.11" |
| 25 | + style={{ |
| 26 | + boxShadow: '0 2px 6px rgba(0, 0, 0, 0.08)', |
| 27 | + }} |
| 28 | +/> |
| 29 | + |
| 30 | +Rstest 0.11 is a transitional release on the road to 1.0. Its focus is to settle several public APIs through breaking changes ahead of the stable release; alongside that, it also brings faster V8 coverage and updated fake timers. |
| 31 | + |
| 32 | +Notable changes: |
| 33 | + |
| 34 | +- [API changes on the road to 1.0](#api-changes) |
| 35 | +- [Faster V8 coverage](#coverage-v8) |
| 36 | +- [Fake timers aligned with `@sinonjs/fake-timers` 15](#fake-timers) |
| 37 | +- [Other improvements](#other-improvements) |
| 38 | + |
| 39 | +## API changes on the road to 1.0 \{#api-changes} |
| 40 | + |
| 41 | +To stabilize the public API before 1.0, Rstest 0.11 adjusts a few surfaces. These are breaking changes, but the migrations are straightforward. |
| 42 | + |
| 43 | +### `TestOptions` moves to the second argument |
| 44 | + |
| 45 | +The [`TestOptions`](/api/runtime-api/test-api/test#testoptions) object (`retry` / `repeats` / `timeout`) for `test` / `it` / `test.each` / `test.for` was previously accepted as the **last** argument, after the test function. With a long test body it was easy to miss, and formatters tended to push it onto a separate trailing line. It now goes in the **second** position, before the test function, matching the shape used by Vitest and Node.js' built-in test runner (`node:test`). |
| 46 | + |
| 47 | +```diff |
| 48 | +- test('name', () => {}, { retry: 2 }); |
| 49 | ++ test('name', { retry: 2 }, () => {}); |
| 50 | +``` |
| 51 | + |
| 52 | +The numeric timeout shorthand is unchanged — the Jest-style `test('name', fn, 1000)` still works as the last argument. `describe` now accepts a `TestOptions` object in the same second-argument position. |
| 53 | + |
| 54 | +### Mock factories are synchronous-only |
| 55 | + |
| 56 | +The `rs.mock` / `rs.doMock` factory type is now synchronous-only. Async factories are prone to hoisting and module-initialization issues, so TypeScript no longer suggests them. For partial mocks, keep the real module via a synchronous `importActual` import attribute and override only the exports you need: |
| 57 | + |
| 58 | +```ts |
| 59 | +import * as actual from './date-utils' with { rstest: 'importActual' }; |
| 60 | + |
| 61 | +rs.mock('./date-utils', () => ({ |
| 62 | + ...actual, |
| 63 | + formatDate: rs.fn().mockReturnValue('2026-03-19'), |
| 64 | +})); |
| 65 | +``` |
| 66 | + |
| 67 | +See [Partially mock modules](/guide/basic/mock#partially-mock-modules) for more details. |
| 68 | + |
| 69 | +### `pool.minWorkers` is removed |
| 70 | + |
| 71 | +`pool.minWorkers` set a floor on retained idle workers, but it only applied under `isolate: false` — the default `isolate: true` never reuses workers, so the option had no effect. It is removed so that `pool` settles on the flat `{ type?, maxWorkers?, execArgv? }` shape; use `pool.maxWorkers` to control file-level parallelism. The internal floor still defaults to `min(maxWorkers, recommended)`, so default warm-worker behavior is unchanged. |
| 72 | + |
| 73 | +### `shard` is CLI-only |
| 74 | + |
| 75 | +Sharding is inherently per-invocation — every CI runner passes a different index, which a value committed to a shared config file cannot express. The `shard` config field is removed from `RstestConfig`; use the [`--shard <index>/<count>`](/guide/basic/cli#sharding-tests) CLI flag instead. The flag and its behavior are unchanged. |
| 76 | + |
| 77 | +```bash |
| 78 | +npx rstest run --shard=1/3 |
| 79 | +``` |
| 80 | + |
| 81 | +## Faster V8 coverage \{#coverage-v8} |
| 82 | + |
| 83 | +On a large bundled project, collecting V8 coverage could spend tens of seconds turning raw V8 data into Istanbul coverage. Rstest 0.11 reworks the conversion path. |
| 84 | + |
| 85 | +Previously, the provider ran every raw V8 entry through the generic `ast-v8-to-istanbul` converter, including many that never reach the final report. Rstest 0.11 uses a converter tailored for Rstest's output, filters out irrelevant entries before converting, and moves the conversion from each worker into the main process. |
| 86 | + |
| 87 | +Measured on the [Rsbuild](https://github.com/web-infra-dev/rsbuild) repository's own test suite (`pnpm test --coverage --coverage.provider=v8`): |
| 88 | + |
| 89 | +| Version | Wall span | Phase sum | |
| 90 | +| ------------------------------- | --------: | --------: | |
| 91 | +| baseline (`ast-v8-to-istanbul`) | 6.05s | 60.11s | |
| 92 | +| Rstest 0.11 | 1.81s | 10.28s | |
| 93 | + |
| 94 | +Compared with the baseline, the reworked path is about **3.34x faster**, and the summed per-file phase time drops **5.85x**. Resolving raw coverage in the main process further cuts Rstest's own processing time (about **2x** on the same project) and lowers CPU usage. The coverage output is unchanged. |
| 95 | + |
| 96 | +There is no configuration change — projects using `coverage.provider: 'v8'` get the speedup automatically. |
| 97 | + |
| 98 | +## Fake timers aligned with `@sinonjs/fake-timers` 15 \{#fake-timers} |
| 99 | + |
| 100 | +Rstest 0.11 upgrades to `@sinonjs/fake-timers@15.4.0` and aligns the fake timers runtime with its API. Two methods are now available: |
| 101 | + |
| 102 | +- `rstest.jumpTimersByTime(ms)` jumps the clock forward by `ms` without running the timers in between, so any timer scheduled within that span fires at most once, at the destination. |
| 103 | +- `rstest.setTickMode({ mode })` switches the clock between manual and auto-advancing modes. |
| 104 | + |
| 105 | +```ts title="example.test.ts" |
| 106 | +import { expect, rstest, test } from '@rstest/core'; |
| 107 | + |
| 108 | +test('jump without running intermediate timers', () => { |
| 109 | + rstest.useFakeTimers(); |
| 110 | + const fn = rstest.fn(); |
| 111 | + |
| 112 | + setInterval(fn, 1000); |
| 113 | + rstest.jumpTimersByTime(5000); |
| 114 | + |
| 115 | + expect(fn).toHaveBeenCalledTimes(1); |
| 116 | +}); |
| 117 | +``` |
| 118 | + |
| 119 | +`rstest.setSystemTime(date)` also works on its own now — without a prior `rstest.useFakeTimers()` call it pins only the global `Date` and leaves the real timers intact, so you can freeze the system clock without taking over `setTimeout` / `setInterval`. |
| 120 | + |
| 121 | +This release also closes a Browser Mode gap. Browser Mode previously aliased `@sinonjs/fake-timers` to a minimal stub that kept a clock-shaped object but did not actually fake browser globals such as `setTimeout` or `Date`. Rstest 0.11 removes that stub and bundles the real implementation, so fake timers behave consistently across Node mode and Browser Mode — including `Date` / `performance`, animation frames, microtasks, and string durations. See [Fake Timers](/api/runtime-api/rstest/fake-timers) for more details. |
| 122 | + |
| 123 | +## Other improvements \{#other-improvements} |
| 124 | + |
| 125 | +- **Clearer error for `rs.spyOn` on ESM namespace exports.** Previously, spying on a read-only ESM namespace export produced an opaque failure; it now produces an actionable error explaining why the binding cannot be reassigned. |
| 126 | +- **`new URL()` and Wasm resolve from on-disk source.** Assets referenced via `new URL(..., import.meta.url)` and Wasm modules now resolve from the original source location, matching runtime behavior. |
| 127 | +- **Shared module state under `isolate: false`.** Imported module state is now shared and context-bound APIs stay live across files when `isolate: false`, so cross-file singletons behave as expected. |
| 128 | +- **Browser Mode project isolation.** Each project now runs in its own Rsbuild instance, fixing cross-project module resolution in multi-project browser runs. |
| 129 | +- **OS-native `testPath`.** `testPath` is now surfaced with the platform's native path separators. |
| 130 | +- **Custom `runCLI` argv.** Programmatic callers can pass a custom `argv` array to `runCLI`, while `process.argv` remains the default. |
| 131 | + |
| 132 | +## Upgrade |
| 133 | + |
| 134 | +Upgrade the `@rstest/*` packages to version 0.11 to get these improvements. Rstest 0.11 includes a few breaking API changes — see [API changes on the road to 1.0](#api-changes) for the migration steps. |
| 135 | + |
| 136 | +For a full list of changes, see the [v0.11.0 release notes](https://github.com/web-infra-dev/rstest/releases/tag/v0.11.0). |
0 commit comments