@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