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 🙏

© 2026 – Pkg Stats / Ryan Hefner

node-tmf

v0.0.3

Published

test for module in nodejs.

Readme

node-tmf

全称 node test module function, 指用于在 Node 端对模块导出的函数做单元测试。

为什么需要

单元测试过程中,一般会 mock 掉不能正常运行的函数,比如数据库操作函数、网络请求函数。于此,常用的工具库有 sinon. 以下为一段示例:

// user.js
const db = require('db');
exports.getUser = (userId) => {
  return db.queryUser(userId);
};

// user.test.js
const db = require('db');
const sinon = require('sinon');
const user = require('./user');

describe('user test', () => {
  it('#getUser()', () => {
    // mock 掉 db 对象中的 `getUser` 方法,使得在接下来的
    // 单元测试执行过程中真正的 `db.getUser` 不会被执行,
    // 这样就能保证即使没有数据库的支持,单元测试也能正常执行。
    const queryUser = sinon.stub(db, 'queryUser');
    const user = user.getUser(123);
    // 还原 mock
    queryUser.restore();

    // 确认`db.queryUser`确实被调用了
    assert.ok(queryUser.called);
    // 确认`db.queryUser`的入参是正确的
    assert.equal(queryUser.lastCallArguments, [123]);
  });
});

如上,我们一般是对对象的某个属性方法进行 mock, 如果某个方法恰巧就是模块默认导出函数,没有挂载的父对象,这种情况就难以处理。比如,简单修改以上的 user.js:

// user.js
const queryUser = require('queryUser');
exports.getUser = (userId) => {
  return queryUser(userId);
};

// 或者
const { queryUser } = require('db');
exports.getUser = (userId) => {
  return queryUser(userId);
};

如上,这两种情况似乎就难以 mock. node-tmf 在这两种情况能发挥较大做用,在示例中说明。

示例

// user.js
const queryUser = require('queryUser');
exports.getUser = (userId) => {
  return queryUser(userId);
};

// 或者
const { queryUser } = require('db');
exports.getUser = (userId) => {
  return queryUser(userId);
};

// user.test.js
const queryUser = require('queryUser');
// 或者如下,视具体情况而定。
const { queryUser } = require('db');

const { mock } = require('node-tmf');
const user = require('./user');

describe('user test', () => {
  it('#getUser()', () => {
    // 将指定的 `queryUser` mock 成你想要的函数,这样,原本真正对接数据库的
    // 函数将不会被执行。
    const mockQueryUser = mock(queryUser, () => ({ name: 'Tom' }));
    const user = user.getUser(123);
    // 还原 mock. 还原后,再次调用 `queryUser` 则又会执行真正的数据库调用。
    queryUser.restore();

    // 确认`queryUser`确实被调用了
    assert.ok(queryUser.called);
    // 确认`queryUser`的入参是正确的
    assert.equal(queryUser.lastCallArguments, [123]);
    // 确认 `queryUser` 的返回值符合预期。注意,这里的返回值是指 mock 后的
    // 函数的返回值
    assert.deepEqual(queryUser.lastReturnValue, { name: 'Tom' });
  });
});

安装

使用 npm

$ npm install node-tmf --save-dev

使用 yarn

$ yarn add node-tmf --dev

使用方法

以 mocha 为例,需要分两步:

第一步

在单元测试运行程序中添加 -r, --require 参数。

// package.json
{
  "scripts": {
    "test": "mocha -r node-tmf/lib/inject.js test/**/*.test.js"
  }
}

第二步

在单元测试代码中,引入 node-tmf 模块对目标方法机型 mock 或者 spy. 具体可参考示例。

API

mock

function(originFunc: Function, mockFunction?: Function): SpyInfo

mock 函数用于将原函数 originFunc mock 成指定的 mockFunction. 当 mockFunction 为空时,则会为期分配一个默认的空函数。空函数的类型自动保持与 originFunc 类型一致,即 Function, AsyncFunction, 或者 GeneratorFunction.

返回说明:mock 方法返回的 SpyInfo 记录着函数的调用情况,具体参数见 返回值SpyInfo说明

spy

function(targetFunc: Function): SpyInfo

spy 函数用于监听并收集目标函数 targetFunc 的执行信息,只是充当一个间谍函数。也就是说,原本的目标函数 targetFunc 逻辑仍然会被执行(mock 的则不会)。

返回说明:spy 方法返回的 SpyInfo 记录着函数的调用情况,具体参数见 返回值SpyInfo说明

返回值SpyInfo说明

{
  called: boolean,      // 函数是否被执行过
  callCount: number,    // 函数被执行次数
  callArgs: any[][],    // 按顺序记录函数每次执行时的入参
  lastCallArgs: any[],  // 函数最后一次执行时的入参
  returnValues: any[],  // 按照顺序记录函数每次执行的返回值
  lastReturnValue: any, // 函数最后一次执行的返回值
}

示例:

// math.js
exports.add = (a, b) => a + b;

// math.test.js
const tmf = require('node-tmf');
const { add } = require('./math');

describe('math', () => {
  it('#add()', () => {
    const spyAdd = tmf.spy(add);
    assert.equal(add(1, 2), 3);
    assert.equal(add(3, 4), 7);
    assert.ok(spyAdd.called);
    assert.equal(spyAdd.callCount, 2);
    assert.deepEqual(spyAdd.callArgs, [[1, 2], [3, 4]]);
    assert.equal(spyAdd.lastCallArgs, [3, 4]);
    assert.equal(spyAdd.returnValues, [3, 7]);
    assert.equal(spyAdd.lastReturnValue, 7);
  });
});

局限性

局限一

不能 mock 或 spy 异步挂载的方法。例如,以下模块的 add 能够被 mock 和 spy, 但 mul 方法则不能被 mock 或 spy.

exports.add = (a, b) => a + b;

setTimeout(() => {
  exports.sub = (a, b) => a - b;
});

局限二

使用 Object.defineProperty 方法并设置 configurable: false 的导出方法不能被 mock 或者 spy. 例如,以下模块的 add 能够被 mock 和 spy, 但 mul 方法则不能被 mock 或 spy.

exports.add = (a, b) => a + b;

Object.defineProperty(exports, 'sub', {
  value: ((a, b) => a - b),
  configurable: false, // configurable 默认为 false
});

局限三

node-tmf 会限制导出函数的层级深度,最多五层。例如,以下 funcInDepth5 方法能够被 mock 或 spy, funcInDepth6 方法则不能。

module.exports = {
  nest1: {
    nest2: {
      nest3: {
        nest4: {
          funcInDepth5: () => 5,
          nest5: {
            funcInDepth6: () => 6,
          },
        },
      },
    },
  },
};

不过,你可以通过设置 TMF_MAX_DEPTH 环境变量,自定义最大层级深度。以上,可以通过设置 TMF_MAX_DEPTH 环境变量为 6 解决问题。

原理说明

node-tmf 通过覆盖 Module.wrapper 重写了 require 方法,在每个 require 的模块中注入了一段代码,这段代码会递归遍历本模块导出方法,并为其包装一层 Proxy, 通过 Proxy 截获函数的调用,并依赖其实现 mockspy 方法。

结语

node-tmf 并不是一个完整的单元测试辅助库,它只是对一些极端情况的补充,实际情况中,一般是以 sinon 为主,node-tmf 为辅。对此,你可以把 node-tmf 当做是一个补丁,专门用于应付模块默认导出函数,和源码中以解构形式导入函数的极端情况。