1.0.0-rc.98 • Published 5 months ago

@exodus/test v1.0.0-rc.98

Weekly downloads
-
License
MIT
Repository
github
Last release
5 months ago

@exodus/test

A runner for node:test, jest, and tape test suites on top of node:test (and any runtime).

It can run your existing tests on all runtimes and also browsers, with snapshots and module mocks.

Features

  • Native ESM, including in Jest tests
  • Esbuild on the fly for babelified ESM interop (enable via --esbuild)
  • TypeScript support in both transform (through tsx, enable via --esbuild) and typestrip (via --typescript) modes
  • Runs on Node.js node:test, Bun, Deno, Electron, v8 CLI, JSC, Hermes, SpiderMonkey, Chrome, Firefox, WebKit, Brave, Microsoft Edge, QuickJS, XS, GraalJS, Escargot, and even engine262.
  • Testsuite-agnostic — can run any file as long as it sets exit code based on test results
  • Built-in Jest compatibility (with --jest), including jest.* global
    • Up to ~10x faster depending on the original setup
    • Actual expect module, also jest-extended and jest-when just work on top
    • Snapshots, including snapshot matchers
    • Function and timer mocks
    • test.concurrent
    • Module mocks, including for ESM modules (already loaded ESM modules can be mocked only on node:test)
    • Loads Jest configuration
  • Built-in network record/replay for offline tests, mocking fetch and WebSocket sessions
  • --drop-network support for guaranteed offline testing
  • Native code coverage via v8 (Node.js or c8), with istanbul reporters
  • GitHub reporter (auto-enabled by default)
  • JSDOM env support
  • Hanging tests error by default (unlike jest)
  • Babel support, picks up your Babel config (enable via --babel)
  • Unlike bun:test, it runs test files in isolated contexts \ Bun leaks globals / side effects between test files (ref), and has incompatible test() lifecycle / order
  • Also features a tape API for drop-in replacement

Engines

Use --engine (or EXODUS_TEST_ENGINE=) to specify one of:

  • node:test — the default one, runs on top of modern Node.js test runner API
  • node:pure — implementation in pure JS, runs on Node.js
  • node:bundle — same as node:pure, but bundles everything into a single file before launching
  • Other runtimes:
    • bun:pure / bun:bundle — Bun, expects bun to be available
    • deno:bundle — Deno (v1 or v2, whichever deno is)
    • electron-as-node:test / electron-as-node:pure / electron-as-node:bundle\ Same as node:*, but uses electron binary.\ The usecase is mostly to test on BoringSSL instead of OpenSSL.
    • electron:bundle — run tests in Electron BrowserWindow without Node.js integration.
  • Browsers:
    • Playwright builds (install Playwright-built engines with exodus-test --playwright install)
      • chromium:playwright — Playwright-built Chromium
      • firefox:playwright — Playwright-built Firefox
      • webkit:playwright — Playwright-built WebKit, close to Safari
      • chrome:playwright — Chrome (system-installed)
      • msedge:playwright — Microsoft Edge (system-installed)
    • Puppeteer (system-provided or upstream builds)
      • chrome:puppeteer — Chrome
      • firefox:puppeteer — Firefox
      • brave:puppeteer — Brave
      • msedge:puppeteer — Microsoft Edge
  • Barebone engines (system-provided or installed with npx jsvu / npx esvu):
    • d8:bundlev8 CLI (Chrome/Blink/Node.js JavaScript engine)
    • jsc:bundleJavaScriptCore (Safari/WebKit JavaScript engine)
    • hermes:bundleHermes (React Native JavaScript engine)
    • spidermonkey:bundleSpiderMonkey (Firefox/Gecko JavaScript engine)
    • quickjs:bundleQuickJS
    • xs:bundleXS
    • graaljs:bundleGraalJS
    • escargot:bundleEscargot
    • engine262:bundle - engine262, the per-spec implementation of ECMA-262 (install with esvu)

Reporter samples

CLI (but uses colors when output supports them, e.g. in terminal):

# tests/jest/expect.mock.test.js
✔ PASS drinkAll > drinks something lemon-flavoured (1.300417ms)
✔ PASS drinkAll > does not drink something octopus-flavoured (0.191791ms)
✔ PASS drinkAll (1.842959ms)
✔ PASS drinkEach > drinkEach drinks each drink (0.360625ms)
✔ PASS drinkEach (0.463416ms)
✔ PASS toHaveBeenCalledWith > registration applies correctly to orange La Croix (0.53325ms)
✔ PASS toHaveBeenCalledWith (0.564166ms)
✔ PASS toHaveBeenLastCalledWith > applying to all flavors does mango last (0.380375ms)
✔ PASS toHaveBeenLastCalledWith (0.473417ms)
# tests/jest/fn.invocationCallOrder.test.js
✔ PASS mock.invocationCallOrder (4.221042ms)

GitHub Actions collapses test results per-file, like this:

See live output in CI

Library

List of exports

  • @exodus/test/nodenode:test API, working under non-Node.js platforms

  • @exodus/test/jestjest implementation

  • @exodus/test/tapetape mock (can also be helpful when moving from tap)

Binary

Just use "test": "exodus-test"

Options

  • --jest — register jest test helpers as global variables, also load jest.config.* configuration options

  • --esbuild — use esbuild loader, also enables Typescript support

  • --babel — use babel loader (slower than --esbuild, makes sense if you have a special config)

  • --coverage — enable coverage, prints coverage output (varies by coverage engine)

  • --coverage-engine c8 — use c8 coverage engine (default), also generates ./coverage/ dirs

  • --coverage-engine node — use Node.js builtint coverage engine

  • --watch — operate in watch mode and re-run tests on file changes

  • --only — only run the tests marked with test.only

  • --passWithNoTests — do not error when no test files were found

  • --write-snapshots — write snapshots instead of verifying them (has --test-update-snapshots alias)

  • --test-force-exit — force exit after tests are done

Jest compatibility

The --jest mode is mostly compatible with Jest. There are some noteworthy differences though. This tool does not hoist mocks, so it is important that a mock is defined before the module that uses it is imported. In ESM, this can be achieved with dynamic imports:

jest.doMock('./hogwarts.js', () => ({
  __esModule: true,
  default: jest.fn(),
}))

const { default: getEntryQualification } = await import('./hogwarts.js')
const { qualifiesForHogwarts } = await import('./wizard.js') // module importing ./hogwarts.js

test('qualifies for Hogwarts', () => {
  // doSomething is a mock function
  getEntryQualification.mockReturnValue(['lumos'])

  expect(qualifiesForHogwarts('potter')).toBe(false)
  getEntryQualification.mockReturnValue([])
  expect(qualifiesForHogwarts('potter')).toBe(true)
})

Note that all modules that transitively import hogwarts.js will have to be imported after the mock is defined.

License

MIT

1.0.0-rc.60

12 months ago

1.0.0-rc.93

6 months ago

1.0.0-rc.92

6 months ago

1.0.0-rc.91

6 months ago

1.0.0-rc.90

6 months ago

1.0.0-rc.86

6 months ago

1.0.0-rc.85

6 months ago

1.0.0-rc.84

6 months ago

1.0.0-rc.83

6 months ago

1.0.0-rc.89

6 months ago

1.0.0-rc.88

6 months ago

1.0.0-rc.87

6 months ago

1.0.0-rc.97

5 months ago

1.0.0-rc.96

5 months ago

1.0.0-rc.95

5 months ago

1.0.0-rc.94

5 months ago

1.0.0-rc.98

5 months ago

1.0.0-rc.71

7 months ago

1.0.0-rc.70

7 months ago

1.0.0-rc.64

7 months ago

1.0.0-rc.63

12 months ago

1.0.0-rc.62

12 months ago

1.0.0-rc.61

12 months ago

1.0.0-rc.68

7 months ago

1.0.0-rc.67

7 months ago

1.0.0-rc.66

7 months ago

1.0.0-rc.65

7 months ago

1.0.0-rc.69

7 months ago

1.0.0-rc.82

6 months ago

1.0.0-rc.81

6 months ago

1.0.0-rc.80

6 months ago

1.0.0-rc.75

6 months ago

1.0.0-rc.74

6 months ago

1.0.0-rc.73

6 months ago

1.0.0-rc.72

7 months ago

1.0.0-rc.79

6 months ago

1.0.0-rc.78

6 months ago

1.0.0-rc.77

6 months ago

1.0.0-rc.76

6 months ago

1.0.0-rc.59

1 year ago

1.0.0-rc.58

1 year ago

1.0.0-rc.57

1 year ago

1.0.0-rc.56

1 year ago

1.0.0-rc.55

1 year ago

1.0.0-rc.54

1 year ago

1.0.0-rc.53

1 year ago

1.0.0-rc.52

1 year ago

1.0.0-rc.51

1 year ago

1.0.0-rc.50

1 year ago

1.0.0-rc.46

1 year ago

1.0.0-rc.45

1 year ago

1.0.0-rc.49

1 year ago

1.0.0-rc.48

1 year ago

1.0.0-rc.47

1 year ago

1.0.0-rc.44

1 year ago

1.0.0-rc.43

1 year ago

1.0.0-rc.42

1 year ago

1.0.0-rc.41

1 year ago

1.0.0-rc.40

1 year ago

1.0.0-rc.39

1 year ago

1.0.0-rc.38

1 year ago

1.0.0-rc.37

1 year ago

1.0.0-rc.36

1 year ago

1.0.0-rc.35

1 year ago

1.0.0-rc.34

1 year ago

1.0.0-rc.33

1 year ago

1.0.0-rc.32

1 year ago

1.0.0-rc.31

1 year ago

1.0.0-rc.30

1 year ago

1.0.0-rc.29

1 year ago

1.0.0-rc.28

1 year ago

1.0.0-rc.27

1 year ago

1.0.0-rc.26

1 year ago

1.0.0-rc.25

1 year ago

1.0.0-rc.24

1 year ago

1.0.0-rc.23

1 year ago

1.0.0-rc.22

1 year ago

1.0.0-rc.21

1 year ago

1.0.0-rc.20

1 year ago

1.0.0-rc.19

1 year ago

1.0.0-rc.18

1 year ago

1.0.0-rc.17

1 year ago

1.0.0-rc.16

1 year ago

1.0.0-rc.15

1 year ago

1.0.0-rc.14

1 year ago

1.0.0-rc.13

1 year ago

1.0.0-rc.12

1 year ago

1.0.0-rc.11

1 year ago

1.0.0-rc.10

1 year ago

1.0.0-rc.9

1 year ago

1.0.0-rc.8

1 year ago

1.0.0-rc.7

1 year ago

1.0.0-rc.6

1 year ago

1.0.0-rc.5

1 year ago

1.0.0-rc.4

1 year ago

1.0.0-rc.3

1 year ago

1.0.0-rc.2

1 year ago

1.0.0-rc.1

1 year ago

1.0.0-rc.0

1 year ago