1.0.0-rc.98 • Published 8 months ago

@exodus/test v1.0.0-rc.98

Weekly downloads
-
License
MIT
Repository
github
Last release
8 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

1 year ago

1.0.0-rc.93

9 months ago

1.0.0-rc.92

9 months ago

1.0.0-rc.91

9 months ago

1.0.0-rc.90

9 months ago

1.0.0-rc.86

9 months ago

1.0.0-rc.85

9 months ago

1.0.0-rc.84

9 months ago

1.0.0-rc.83

9 months ago

1.0.0-rc.89

9 months ago

1.0.0-rc.88

9 months ago

1.0.0-rc.87

9 months ago

1.0.0-rc.97

8 months ago

1.0.0-rc.96

8 months ago

1.0.0-rc.95

8 months ago

1.0.0-rc.94

8 months ago

1.0.0-rc.98

8 months ago

1.0.0-rc.71

10 months ago

1.0.0-rc.70

10 months ago

1.0.0-rc.64

10 months ago

1.0.0-rc.63

1 year ago

1.0.0-rc.62

1 year ago

1.0.0-rc.61

1 year ago

1.0.0-rc.68

10 months ago

1.0.0-rc.67

10 months ago

1.0.0-rc.66

10 months ago

1.0.0-rc.65

10 months ago

1.0.0-rc.69

10 months ago

1.0.0-rc.82

9 months ago

1.0.0-rc.81

9 months ago

1.0.0-rc.80

9 months ago

1.0.0-rc.75

9 months ago

1.0.0-rc.74

9 months ago

1.0.0-rc.73

9 months ago

1.0.0-rc.72

10 months ago

1.0.0-rc.79

9 months ago

1.0.0-rc.78

9 months ago

1.0.0-rc.77

9 months ago

1.0.0-rc.76

9 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

2 years ago

1.0.0-rc.35

2 years ago

1.0.0-rc.34

2 years ago

1.0.0-rc.33

2 years ago

1.0.0-rc.32

2 years ago

1.0.0-rc.31

2 years ago

1.0.0-rc.30

2 years ago

1.0.0-rc.29

2 years ago

1.0.0-rc.28

2 years ago

1.0.0-rc.27

2 years ago

1.0.0-rc.26

2 years ago

1.0.0-rc.25

2 years ago

1.0.0-rc.24

2 years ago

1.0.0-rc.23

2 years ago

1.0.0-rc.22

2 years ago

1.0.0-rc.21

2 years ago

1.0.0-rc.20

2 years ago

1.0.0-rc.19

2 years ago

1.0.0-rc.18

2 years ago

1.0.0-rc.17

2 years ago

1.0.0-rc.16

2 years ago

1.0.0-rc.15

2 years ago

1.0.0-rc.14

2 years ago

1.0.0-rc.13

2 years ago

1.0.0-rc.12

2 years ago

1.0.0-rc.11

2 years ago

1.0.0-rc.10

2 years ago

1.0.0-rc.9

2 years ago

1.0.0-rc.8

2 years ago

1.0.0-rc.7

2 years ago

1.0.0-rc.6

2 years ago

1.0.0-rc.5

2 years ago

1.0.0-rc.4

2 years ago

1.0.0-rc.3

2 years ago

1.0.0-rc.2

2 years ago

1.0.0-rc.1

2 years ago

1.0.0-rc.0

2 years ago