@magic/test

Utility Belt

@magic/test exports some utility functions that make working with complex test workflows simpler.

deep

Exported from @magic/deep, deep equality and comparison utilities.

import { deep, is } from '@magic/test'export default [  {    fn: () => ({ a: 1, b: 2 }),    expect: deep.equal({ a: 1, b: 2 }),    info: 'deep equals comparison',  },  {    fn: () => ({ a: 1 }),    expect: deep.different({ a: 2 }),    info: 'deep different comparison',  },  {    fn: () => ({ a: { b: 1 } }),    expect: deep.equal({ a: { b: 1 } }),    info: 'nested deep equality',  },]

Available functions:

  • deep.equal(a, b) - deep equality check
  • deep.different(a, b) - deep difference check
  • deep.contains(container, item) - deep inclusion check
  • deep.changes(a, b) - get differences between objects

fs

Exported from @magic/fs, file system utilities.

import { fs } from '@magic/test'export default [  {    fn: async () => {      const content = await fs.readFile('./package.json', 'utf-8')      return content.includes('name')    },    expect: true,    info: 'read file content',  },]

Common methods:

  • fs.readFile(path, encoding) - read file content
  • fs.writeFile(path, data) - write file content
  • fs.exists(path) - check if file exists
  • fs.mkdir(path, options) - create directory
  • fs.rmdir(path) - remove directory
  • fs.stat(path) - get file stats
  • fs.readdir(path) - read directory contents
  • Plus async versions in fs.promises

curry

Currying can be used to split the arguments of a function into multiple nested functions. This helps if you have a function with complicated arguments that you just want to quickly shim.

import { curry } from '@magic/test'const compare = (a, b) => a === bconst curried = curry(compare)const shimmed = curried('shimmed_value')export default {  fn: shimmed('shimmed_value'),  expect: true,  info: 'expect will be called with a and b and a will equal b',}

log

Logging utility for test output. Colors supported automatically.

import { log } from '@magic/test'log.debug('Debug info')log.info('Something happened')log.warn('Heads up')log.error('Something went wrong')log.critical('Game over')

Supports multiple arguments:

log.info('Testing', library, 'at version', version)

vals

Exports JavaScript type constants for testing against any value. Useful for fuzzing and property-based testing.

import { vals, is } from '@magic/test'export default [  { fn: () => 'test', expect: is.string, info: 'test if value is a string' },  { fn: () => vals.true, expect: true, info: 'boolean true value' },  { fn: () => vals.email, expect: is.email, info: 'valid email format' },  { fn: () => vals.error, expect: is.error, info: 'error instance' },]

Available Constants:

  • Primitives: true, false, number, num, float, int, string, str
  • Empty values: nil, emptystr, emptyobject, emptyarray, undef
  • Collections: array, object, obj
  • Time: date, time
  • Errors: error, err
  • Colors: rgb, rgba, hex3, hex6, hexa4, hexa8
  • Other: func, truthy, falsy, email, regexp

env

Environment detection utilities for conditional test behavior.

Available utilities:

  • isNodeProd - checks if NODE_ENV is set to production
  • isNodeDev - checks if NODE_ENV is set to development
  • isProd - checks if -p flag is passed to the CLI
  • isVerbose - checks if -l flag is passed to the CLI
  • getErrorLength - returns error length limit from MAGIC_TEST_ERROR_LENGTH env var (0 = unlimited)
import { env, isProd, isTest, isDev } from '@magic/test'export default [  {    fn: env.isNodeProd,    expect: process.env.NODE_ENV === 'production',    info: 'checks if NODE_ENV is production',  },  {    fn: env.isNodeDev,    expect: process.env.NODE_ENV === 'development',    info: 'checks if NODE_ENV is development',  },  {    fn: env.isProd,    expect: process.argv.includes('-p'),    info: 'checks if -p flag is passed',  },  {    fn: env.isVerbose,    expect: process.argv.includes('-l'),    info: 'checks if -l flag is passed',  },  {    fn: env.getErrorLength,    expect: 70,    info: 'get error length limit',  },]

Environment Constants

These boolean constants reflect the current NODE_ENV:

import { isProd, isTest, isDev } from '@magic/test'export default [  { fn: isProd, expect: process.env.NODE_ENV === 'production' },  { fn: isTest, expect: process.env.NODE_ENV === 'test' },  { fn: isDev, expect: process.env.NODE_ENV === 'development' },]

promises

Helper function to wrap nodejs callback functions and promises with ease. Handles the try/catch steps internally and returns a resolved or rejected promise.

import { promise, is } from '@magic/test'export default [  {    fn: promise(cb => setTimeout(() => cb(null, true), 200)),    expect: true,    info: 'handle promises in a nice way',  },  {    fn: promise(cb => setTimeout(() => cb(new Error('error'), 200)),    expect: is.error,    info: 'handle promise errors in a nice way',  },]

http

HTTP utility for making requests in tests. Supports both HTTP and HTTPS.

import { http } from '@magic/test'export default [  {    fn: http.get('https://api.example.com/data'),    expect: { success: true },    info: 'fetches data from API',  },  {    fn: http.post('https://api.example.com/users', { name: 'John' }),    expect: { id: 1, name: 'John' },    info: 'creates a new user',  },  {    fn: http.post('http://localhost:3000/data', 'raw string'),    expect: 'raw string',    info: 'posts raw string data',  },]

Error Handling:

import { http, is } from '@magic/test'export default [  {    fn: http.get('https://invalid-domain-that-does-not-exist.com'),    expect: is.error,    info: 'rejects on network error',  },  {    fn: http.get('https://api.example.com/nonexistent'),    expect: res => res.status === 404,    info: 'handles 404 responses',  },]

Note: HTTP module automatically handles:

  • Protocol detection (HTTP vs HTTPS)
  • JSON parsing for responses with Content-Type: application/json
  • Raw string returns for non-JSON responses
  • rejectUnauthorized: false for self-signed certificates

HttpOptions

import type { HttpOptions } from '@magic/test'

| Option | Type | Default | Description |

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

| timeout | number | 30000 | Request timeout in milliseconds |

| rejectUnauthorized | boolean | true | Reject self-signed certs |

| maxSize | number | - | Maximum response size in bytes |

| requestOptions | RequestOptions | - | Additional request options |

trycatch

allows to catch and test functions without bubbling the errors up into the runtime

import { is, tryCatch } from '@magic/test'const throwing = () => throw new Error('oops')const healthy = () => trueexport default [  {    fn: tryCatch(throwing()),    expect: is.error,    info: 'function throws an error',  },  {    fn: tryCatch(healthy()),    expect: true,    info: 'function does not throw',  },]

error

exports @magic/error which returns errors with optional names.

import { error } from '@magic/test'export default [  {    fn: tryCatch(error('Message', 'E_NAME')),    expect: e => e.name === 'E_NAME' && e.message === 'Message',    info: 'Errors have messages and (optional) names.',  },]

mock

Mock and spy utilities for function testing.

import { mock, tryCatch } from '@magic/test'export default [  {    fn: () => {      const spy = mock.fn()      spy('arg1')      return spy.calls.length === 1 && spy.calls[0][0] === 'arg1'    },    expect: true,    info: 'mock.fn tracks call arguments',  },  {    fn: () => {      const spy = mock.fn().mockReturnValue('mocked')      return spy() === 'mocked'    },    expect: true,    info: 'mock.fn.mockReturnValue sets return value',  },  {    fn: async () => {      const spy = mock.fn().mockThrow(new Error('fail'))      const caught = await tryCatch(spy)()      return caught instanceof Error    },    expect: true,    info: 'mock.fn.mockThrow works with tryCatch',  },  {    fn: () => {      const obj = { greet: () => 'hello' }      const spy = mock.spy(obj, 'greet', () => 'world')      const result = obj.greet()      spy.mockRestore()      return result === 'world' && obj.greet() === 'hello'    },    expect: true,    info: 'mock.spy replaces and restores methods',  },]

mock.fn properties

  • calls - Array of all call arguments
  • returns - Array of all return values
  • errors - Array of all thrown errors (null for non-throwing calls)
  • callCount - Number of times called

mock.fn methods

  • mockReturnValue(value) - Set return value (chainable)
  • mockThrow(error) - Set error to throw (chainable)
  • getCalls() - Get all call arguments
  • getReturns() - Get all return values
  • getErrors() - Get all thrown errors

mock.log

Logging utilities that respect NODE_ENV for conditional output:

import { mock } from '@magic/test'mock.log.log('Debug info')      // Logs if not NODE_ENV=productionmock.log.warn('Heads up')       // Logs if not NODE_ENV=productionmock.log.error('Something went wrong')  // Always logsmock.log.time('operation')     // Logs timing if not NODE_ENV=productionmock.log.timeEnd('operation')   // Logs timing end if not NODE_ENV=production

has

Functions for asserting object properties without needing explicit type annotations or stringifying functions.

import { has, is } from '@magic/test'

has.property(key, check)

Check a single property. Accepts either a predicate or a literal value.

// With predicate{  fn: () => getUser(),  expect: has.property('name', is.string),  info: 'user name is a string',}// With literal value (uses is.deep.equal){  fn: () => getUser(),  expect: has.property('age', 25),  info: 'user age is 25',}

has.properties(spec)

Check multiple properties. Mix predicates and literal values.

{  fn: () => getUser(),  expect: has.properties({ name: is.string, age: is.num }),  info: 'user has required properties',}

has.any(spec)

Check at least one property matches. Accepts predicates or literals.

{  fn: () => parseResult(),  expect: has.any({ error: is.error, data: is.object }),  info: 'result has either error or data',}

has.nested(path, predicate)

Check a nested property path.

{  fn: () => getData(),  expect: has.nested('user.profile.name', is.string),  info: 'deep nested property exists',}

has.string(substring)

Check if value is a string containing substring.

{  fn: () => error.message,  expect: has.string('failed to connect'),  info: 'error message contains helpful context',}

has.key(keyName)

Check if object has a specific key.

has.keys(keyNames[])

Check if object has all specified keys.

has.includes(item)

Check if array or string contains item (uses deep.equal for arrays).

has.oneOf(options[])

Check if value equals one of the options (uses deep.equal).

has.matches(regex)

Check if string matches regex pattern.

version

The version plugin checks your code according to a spec defined by you. This is designed to warn you on changes to your exports.

Internally, the version function calls @magic/types and all functions exported from it are valid type strings in version specs.

import { version } from '@magic/test'// import your lib as your codebase requires// import * as lib from '../src/index.js'// import lib from '../src/index.jsconst spec = {  stringValue: 'string',  numberValue: 'number',  objectValue: [    'obj',    {      key: 'Willbechecked',    },  ],  objectNoChildCheck: ['obj', false],}export default version(lib, spec)

Using `['obj', false]` in a spec will test that the parent is an object without checking the key/value pairs inside.

DOM Environment

@magic/test automatically initializes a DOM environment when imported, making browser APIs available in Node.js.

Available globals

  • Core: document, window, self, navigator, location, history
  • DOM types: Node, Element, HTMLElement, SVGElement, Document, DocumentFragment
  • Events: Event, CustomEvent, MouseEvent, KeyboardEvent, InputEvent, TouchEvent, PointerEvent
  • Forms: FormData, File, FileList, Blob
  • Networking: URL, URLSearchParams, XMLHttpRequest, fetch, WebSocket
  • Storage: Storage, sessionStorage, localStorage
  • Observers: MutationObserver, IntersectionObserver, ResizeObserver
  • Timers: setTimeout, setInterval, requestAnimationFrame

DOM Utilities

import { initDOM, getDocument, getWindow } from '@magic/test'// Get the document and window instancesconst doc = getDocument()const win = getWindow()// Manually re-initialize if neededinitDOM()

Canvas/Image Polyfills

  • new Image() - Parses PNG data URLs to extract dimensions
  • canvas.getContext("2d") - Returns node-canvas context
  • canvas.toDataURL() - Serializes canvas to data URL