npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

named-patch

v1.0.13

Published

Higher order function for patching named export functions.

Downloads

480

Readme

>named-patch

Enable monkey patching of named ESM exports.

npm package License

Contents

Introduction

Enables monkey patching of a named ESM export.

Monkey Patching is a common way to replace a function with a mock or listener (such as Sinon.stub). In most JS interfaces, this can be done natively by re-writing the property on the parent object.

For example:

const mock = () => '<mocked>';
process.exit = mock;

import Path from 'node:path';
Path.join = mock;

process.exit() // '<mocked>'
Path.join() // '<mocked>'

However this is not possible with named imports/exports, as modules (generated via namespace imports import * as Namespace) are not writable, and importing the name directly does not provide a parent object to write to.

import * as NamespacePath from 'node:path';

const mock = () => '<mocked>';
// TypeError: Cannot assign to read only property 'join' of object '[object Module]'
NamespacePath.join = mock;

This generally requires wrapping named imports into a single container, which defeats the benefits of named imports (e.g. tree shaking). This is how the Path.join patching works in the first example.

import { join } from 'node:path';

const mock = () => '<mocked>';
const container = { join };
container.join = mock;

container.join(); // '<mocked>'

The solution is to wrap named exports in a higher-order-function that proxies requests to the input function by default, and enables patching that method in testing environments.

Install

npm i named-patch

Example

// a.js
import { patch } from 'named-patch';

export const randName = patch(<T extends string>(names: T[]) => names[Math.trunc(Math.random() * names.length)]);

// b.js
import { patchKey } from 'named-patch';
import { randName } from './a.js';

rand(['foo', 'bar']); // 'foo'
// Still supports generics
rand<'abc' | 'xyz'>(['abc', 'xyz']); // 'xyz'

rand[patchKey] = () => '<custom>';
rand(['foo', 'bar']); // '<custom>'

Usage

named-patch is an ESM module. That means it must be imported. To load from a CJS module, use dynamic import const { patch } = await import('named-patch');.

This module exports 2 different packages depending on the environment. By default it is the noop module which only exports the patch method that returns the method unchanged.

Using a Condition of --conditions=patchable will enable the patchKey and getPatched methods.

This enforces a best practice that by default monkey patching and stubbing is a testing-specific pattern that should be omitted in production.

Conditions may be set a number of ways:

  • node --conditions=patchable ./my-script.js
  • NODE_OPTIONS='--conditions=patchable' node ./my-script.js
    • Frameworks like mocha provide setting these conditions via config's node-option.

API

patch(fn)

Returns a wrapper around fn that by default calls fn internally. The behavior of the wrapper can be overwritten by writing the patchKey property of the wrapper.

It will have the same typescript properties as the input function (with addition of patchKey property) with full support for generics.

Async functions and references to this are handled appropriately.

Patching a function is idempotent and cache-able.

import { patch } from 'named-patch';

const original = () => {};
const patched = patch(original);
patch(original) === patched; // true
patch(patched) === patched; // true

It is always possible to get a consistent patchable version of a method by passing it to the patch method. Therefore cases where a patching happens internally to a module and not re-exported is still patchable.

// a.js
import { readFileSync } from 'node:fs/promises';
import { patch } from 'named-patch';

const patchedReadFileSync = patch(readFileSync);

export const getFileData = (fileName: string): string => patchedReadFileSync(filename, 'utf8');

// b.js
import { readFileSync } from 'node:fs/promises';
import { patch, patchKey } from 'named-patch';
import { getFileData } from './a.js';

const patchedReadFileSync = patch(readFileSync);
patchedReadFileSync = () => '<fake-data>';

getFileData('<file-name>'); // '<fake-data>';

patchKey

A unique symbol written onto all wrappers returned from patch.

Will only be exported from module when patchable condition is set.

getPatched(fn)

Helper method that will return the patched version of a function. Throws if the method has never been patched.

Useful for test environments where the existence of a patch is being tested, and relying on patch's idempotency may accidentally write the patch for the first time.

Will only be exported from module when patchable condition is set.