@magic/test

CLI & Usage

package.json (recommended)

Add the magic/test bin scripts to package.json:

{  "scripts": {    "test": "t -p",    "coverage": "t",  },  "devDependencies": {    "@magic/test": "github:magic/test"  }}

Then use the npm run scripts:

npm testnpm run coverage

Globally (not recommended)

You can install this library globally, but the recommendation is to add the dependency and scripts to the package.json file.

This both explains to everyone that your app has these dependencies as well as keeping your bash free of clutter.

npm i -g @magic/test// run tests in production modet -p// run tests and get coverage in verbose modet

CLI Flags

Available command-line flags:

  • -p, --production, --prod - Run tests without coverage (faster)
  • -l, --verbose, --loud - Show detailed output including passing tests
  • -i, --include - Files to include in coverage
  • -e, --exclude - Files to exclude from coverage
  • --shards N - Total number of shards to split tests across
  • --shard-id N - Shard ID (0-indexed) to run
  • -w, --workers N - Max parallel workers (default: auto)
  • --help - Show help text

Note: `--shards` and `--shard-id` must be used together. `--shard-id` is 0-indexed (0 to N-1).

Sharding Tests

Run tests in parallel across multiple processes to speed up large test suites:

# Run 4 shards, this is shard 0 (of 0-3)t --shards 4 --shard-id 0# Run shard 1t --shards 4 --shard-id 1# Combine with other flagst -p --shards 4 --shard-id 2

Tests are distributed deterministically using a hash of the test file path, ensuring each test always runs in the same shard.

Add to your package.json for CI/CD:

{  "scripts": {    "test": "t -p",    "test:shard:0": "t -p --shards 4 --shard-id 0",    "test:shard:1": "t -p --shards 4 --shard-id 1",    "test:shard:2": "t -p --shards 4 --shard-id 2",    "test:shard:3": "t -p --shards 4 --shard-id 3"  }}

Or use a single command to run all shards in parallel:

# Run all 4 shards in parallel and wait for all to completenpm run test:shard:0 & npm run test:shard:1 & npm run test:shard:2 & npm run test:shard:3 & wait

Exit Codes

@magic/test returns specific exit codes to indicate test results:

| Exit Code | Meaning |

| --------- | ------- |

| 0 | All tests passed |

| 1 | One or more tests failed |

# Run tests and check exit codenpm testecho "Exit code: $?"  # 0 = success, 1 = failure

Verbose Output

The -l (or --verbose, --loud) flag enables detailed output:

# Shows all tests including passing onest -l

What verbose mode shows:

  • All test results (not just failures)
  • Individual test execution time
  • Full test names with suite hierarchy
  • Detailed error messages with stack traces

Default mode (without -l):

  • Only shows failing tests
  • Shows summary only for passing suites
  • Faster output for large test suites

Example output without -l:

### Testing package: my-lib/addition.js => Pass: 3/3 100%/multiplication.js => Pass: 4/4 100%Ran 7 tests in 12ms. Passed 7/7 100%

Example output with -l:

### Testing package: my-lib▶ addition  ✔ adds two positive numbers (1.2ms)  ✔ handles zero correctly (0.8ms)  ✔ handles negative numbers (0.9ms)▶ multiplication  ✔ multiplies by zero (0.7ms)  ✔ multiplies by one (0.6ms)  ✔ multiplies two positives (0.8ms)  ✔ handles negative numbers (0.9ms)Ran 7 tests in 12ms. Passed 7/7 100%

Performance Tips

Follow these tips to get the most out of @magic/test:

Use the -p flag for development

# Fast mode - no coverage, only shows failuresnpm test# ort -p

Shard large test suites

# Split tests across multiple processest --shards 4 --shard-id 0

Minimize async overhead

# Slower: unnecessary asyncexport default {  fn: async () => {    return true  },  expect: true,}# Faster: sync testexport default {  fn: () => true,  expect: true,}

Use local state instead of globals

# Slower: global state requires isolationexport const __isolate = true# Faster: local state is naturally isolatedexport default [  {    fn: () => {      const counter = 0      return ++counter    },    expect: 1,  },]

Batch related tests

# Faster: single suite with multiple testsexport default [  { fn: () => add(1, 2), expect: 3 },  { fn: () => add(0, 0), expect: 0 },  { fn: () => add(-1, 1), expect: 0 },]

Common Pitfalls

Avoid these common mistakes when writing tests:

1. Forgetting to return in async tests

# Wrong: promise resolves before test checks resultexport default {  fn: async () => {    const result = await someAsyncFunction()    // missing return!  },  expect: true,}# Correct:export default {  fn: async () => {    return await someAsyncFunction()  },  expect: true,}

2. Not wrapping callback functions

# Wrong: function gets called immediatelyexport default {  fn: doSomething(),  // executes immediately!  expect: true,}# Correct: wrap in function to defer executionexport default {  fn: () => doSomething(),  expect: true,}

3. Mutating shared state between tests

# Wrong: counter persists between testslet counter = 0export default [  { fn: () => ++counter, expect: 1 },  { fn: () => ++counter, expect: 2 }, // fails! counter is now 1]# Correct: use local state or reset in beforeEachlet counter = 0const beforeEach = () => { counter = 0 }export default {  beforeEach,  tests: [    { fn: () => ++counter, expect: 1 },    { fn: () => ++counter, expect: 1 }, // passes - reset before each  ],}

4. Using the wrong equality check

# Wrong: checks reference equalityexport default {  fn: () => [1, 2, 3],  expect: [1, 2, 3], // fails! different arrays}# Correct: use @magic/types for deep comparisonimport { is } from '@magic/test'export default {  fn: () => [1, 2, 3],  expect: is.deep.equal([1, 2, 3]),}

5. Not awaiting async operations

# Wrong: test finishes before promise resolvesexport default {  fn: () => {    setTimeout(() => {      // This never gets checked!    }, 100)  },  expect: true,}# Correct: return the promiseexport default {  fn: () => new Promise(resolve => {    setTimeout(() => resolve(true), 100)  }),  expect: true,}# Or use the promise helper:import { promise } from '@magic/test'export default {  fn: promise(cb => setTimeout(() => cb(null, true), 100)),  expect: true,}

6. Incorrect hook usage

# Wrong: before/after hooks on individual tests, not suitesexport default [  {    fn: () => true,    beforeAll: () => {}, // wrong! beforeAll is for suites    afterAll: () => {},    expect: true,  },]# Correct: hooks at suite levelconst beforeAll = () => {}const afterAll = () => {}export default {  beforeAll,  afterAll,  tests: [    { fn: () => true, expect: true },  ],}