Ensure Functions are Called Correctly with JavaScript Mocks

Often when writing JavaScript tests and mocking dependencies, you’ll want to verify that the function was called correctly. That requires keeping track of how often the function was called and what arguments it was called with. That way we can make assertions on how many times it was called and ensure it was called with the right arguments.

Function to be mocked: utils.js

// returns the winning player or null for a tie
// Let's pretend this isn't using Math.random() but instead
// is making a call to some third party machine learning
// service that has a testing environment we don't control
// and is unreliable so we want to mock it out for tests.
function getWinner(player1, player2) {
const winningNumber = Math.random();
return winningNumber < 1 / 3
? player1
: winningNumber < 2 / 3
? player2
: null;
} module.exports = {getWinner};

Implementaion: thumbwar.js

const utils = require("./utils");

function thumbWar(player1, player2) {
const numberToWin = 2;
let player1Wins = 0;
let player2Wins = 0;
while (player1Wins < numberToWin && player2Wins < numberToWin) {
const winner = utils.getWinner(player1, player2);
if (winner === player1) {
player1Wins++;
} else if (winner === player2) {
player2Wins++;
}
}
return player1Wins > player2Wins ? player1 : player2;
} module.exports = thumbWar;

Testing:

const thumbWar = require("./thumbwar");
const utils = require("./utils");
const assert = require("assert"); test("returns winner", () => {
const originalGetWinner = utils.getWinner;
utils.getWinner = jest.fn((p1, p2) => p1); // eslint-disable-line no-unused-vars
const winner = thumbWar("KCD", "KW");
expect(winner).toBe("KCD");
// check the params are correct
expect(utils.getWinner.mock.calls).toEqual([["KCD", "KW"], ["KCD", "KW"]]);
// check the fn has been called number of times
expect(utils.getWinner).toHaveBeenCalledTimes(2);
// check each time call the fn with the correct params
expect(utils.getWinner).toHaveBeenNthCalledWith(1, "KCD", "KW");
expect(utils.getWinner).toHaveBeenNthCalledWith(2, "KCD", "KW"); utils.getWinner = originalGetWinner;
});

Here we are using 'jest.fn' to mock the function.

We can also create a mock fn by ourselves.

function fn(impl) {
const mockFn = (...args) => {
mockFn.mock.calls.push(args);
return impl(...args);
};
mockFn.mock = {calls: []};
return mockFn;
}
test("returns winner: fn", () => {
const originalGetWinner = utils.getWinner;
utils.getWinner = fn((p1, p2) => p1); // eslint-disable-line no-unused-vars
const winner = thumbWar("KCD", "KW");
assert.strictEqual(winner, "KCD");
assert.deepStrictEqual(utils.getWinner.mock.calls, [
["KCD", "KW"],
["KCD", "KW"],
]);
utils.getWinner = originalGetWinner;
});

Restore the Original Implementation of a Mocked JavaScript Function with jest.spyOn

With our current usage of the mock function we have to manually keep track of the original implementation so we can cleanup after ourselves to keep our tests idempotent (moonkey patching). Let’s see how jest.spyOn can help us avoid the bookkeeping and simplify our situation.

test("returns winner", () => {
//const originalGetWinner = utils.getWinner;
//utils.getWinner = jest.fn((p1, p2) => p1); // eslint-disable-line no-unused-vars
jest.spyOn(utils, "getWinner");
utils.getWinner.mockImplementation((p1, p2) => p1); // eslint-disable-line no-unused-vars
const winner = thumbWar("KCD", "KW");
expect(winner).toBe("KCD");
expect(utils.getWinner.mock.calls).toEqual([["KCD", "KW"], ["KCD", "KW"]]);
expect(utils.getWinner).toHaveBeenCalledTimes(2);
expect(utils.getWinner).toHaveBeenNthCalledWith(1, "KCD", "KW");
expect(utils.getWinner).toHaveBeenNthCalledWith(2, "KCD", "KW"); // utils.getWinner = originalGetWinner;
utils.getWinner.mockRestore();
});

Here we are using jest.spyOn function.

We can also write spyOn function by ourselves.

function fn(impl = () => {}) {
const mockFn = (...args) => {
mockFn.mock.calls.push(args);
mockFn.mockImplementation = newImpl => (impl = newImpl);
return impl(...args);
};
mockFn.mock = {calls: []};
return mockFn;
} function spyOn(obj, prop) {
// store the origianl fn
const originalValue = obj[prop];
// assign new mock fn
obj[prop] = fn;
// add restore fn
obj[prop].mockRestore = () => (obj[prop] = originalValue);
} test("returns winner: fn", () => {
spyOn(utils, "getWinner");
utils.getWinner.mockImplementation = fn((p1, p2) => p1); // eslint-disable-line no-unused-vars
const winner = thumbWar("KCD", "KW");
assert.strictEqual(winner, "KCD");
assert.deepStrictEqual(utils.getWinner.mock.calls, [
["KCD", "KW"],
["KCD", "KW"],
]);
utils.getWinner.mockRestore();
});

Mock a JavaScript module in a test

So far we’re still basically monkey-patching the utils module which is fine, but could lead to problems in the future, especially if we want to mock a ESModule export which doesn’t allow this kind of monkey-patching on exports. Instead, let’s mock the entire module so when our test subject requires the file they get our mocked version instead.

To mock a whole module. we can use 'jest.mock':

const thumbWar = require("./thumbwar");
const utils = require("./utils");
const assert = require("assert"); jest.mock("./utils", () => {
return {
getWinner: jest.fn((p1, p2) => p1), // eslint-disable-line no-unused-vars
};
}); test("returns winner", () => { const winner = thumbWar("KCD", "KW");
expect(winner).toBe("KCD");
expect(utils.getWinner.mock.calls).toEqual([["KCD", "KW"], ["KCD", "KW"]]);
expect(utils.getWinner).toHaveBeenCalledTimes(2);
expect(utils.getWinner).toHaveBeenNthCalledWith(1, "KCD", "KW");
expect(utils.getWinner).toHaveBeenNthCalledWith(2, "KCD", "KW"); utils.getWinner.mockReset();
});

Now we don't need to mock the 'getWinner' function inside test, 'jest.mock' can be used anywhere, jest will make sure it mock will be hoisted to the top.

Make a shared JavaScript mock module

Often you’ll want to mock the same file throughout all the tests in your codebase. So let’s make a shared mock file in Jest's __mocks__ directory which Jest can load for us automatically.

__mocks__/utils.js:

module.exports = {
getWinner: jest.fn((p1, p2) => p1), // eslint-disable-line no-unused-vars
};
const thumbWar = require("../thumbwar");
const utils = require("../utils");
const assert = require("assert"); jest.mock("../utils"); test("returns winner", () => {
const winner = thumbWar("KCD", "KW");
expect(winner).toBe("KCD");
expect(utils.getWinner.mock.calls).toEqual([["KCD", "KW"], ["KCD", "KW"]]);
expect(utils.getWinner).toHaveBeenCalledTimes(2);
expect(utils.getWinner).toHaveBeenNthCalledWith(1, "KCD", "KW");
expect(utils.getWinner).toHaveBeenNthCalledWith(2, "KCD", "KW"); utils.getWinner.mockReset();
});

[Testing] JavaScript Mocking Fundamentals的更多相关文章

  1. Googletest - Google Testing and Mocking Framework

    Googletest - Google Testing and Mocking Framework https://github.com/google/googletest

  2. [Testing] Static Analysis Testing JavaScript Applications

    The static code analysis and linting tool ESLint is the de-facto standard for linting JavaScript pro ...

  3. 12款简化 Web 开发的 JavaScript 开发框架

    前端框架简化了开发过程中,像 Bootstrap 和 Foundation 就是前端框架的佼佼者.在这篇文章了,我们编制了一组新鲜的,实用的,可以帮助您建立高质量的 Web 应用程序的 JavaScr ...

  4. JavaScript测试工具比较: QUnit, Jasmine, and Mocha

    1. QUnit A JavaScript Unit Testing framework. QUnit is a powerful, easy-to-use JavaScript unit testi ...

  5. 15款加速 Web 开发的 JavaScript 框架

    JavaScript 可以通过多种方式来创建交互式的网站和 Web 应用程序.利用 JavaScript,可以让你移动 HTML 元素,创建各种各样的自定义动画,给你的访问者更好的终端用户体验. 对于 ...

  6. 关于JavaScript测试工具:QUnit, Jasmine, MoCha

    在进行前端开发过程中,在某些场景下,需要通过编写单元测试来提高代码质量.而JavaScript常用的单元测试框架有这几个:QUnit, Jasmine, MoCha.下面就基于这三个工具,简单做一比较 ...

  7. Top JavaScript Frameworks, Libraries & Tools and When to Use Them

    It seems almost every other week there is a new JavaScript library taking the web community by storm ...

  8. 近期流行的JavaScript框架与主题

    [新年快乐]2017年你应该关注的JavaScript框架与主题 2017-01-01 王下邀月熊 JavaScript JavaScript的繁荣促生了很多优秀的技术.框架与工具库,这空前的繁荣也给 ...

  9. JavaScript引擎基本原理: 优化prototypes

    原文链接: JavaScript engine fundamentals: optimizing prototypes 这篇文章介绍了一些JavaScript引擎常用的优化关键点, 并不只是Bened ...

随机推荐

  1. element使用心得

    Table Table 常用属性解释 数据过滤,filter过滤器 <el-table-column width="200" show-overflow-tooltip la ...

  2. mysql 慢查询日志 mysqldumpslow 工具

    文章来源:https://www.cnblogs.com/hello-tl/p/9229676.html 1.使用Mysql慢查询日志配置 查看慢查询日志是否开启 OFF关闭 ON开启 show va ...

  3. 爬取斗图网图片,使用xpath格式来匹配内容,对请求伪装成浏览器, Referer 防跨域请求

    6.21自我总结 一.爬取斗图网 1.摘要 使用xpath匹配规则查找对应信息文件 将请求伪装成浏览器 Referer 防跨域请求 2.爬取代码 #导入模块 import requests #爬取网址 ...

  4. DELL R730 服务器拷贝大文件

    从服务器上拷贝大文件,通过USB拷贝,写入速度很慢,而且拷贝到100多G的时候直接卡死. 原因:服务器的USB是2.0,传输速率很慢. 解决方法: 找一台笔记本,USB 接口是3.0的,通过网络共享传 ...

  5. Ubuntu18.04 无法解析域名

    解决方法: 首先先输入以下4条命令 1. sudo lshw -numeric -class network2. sudo ifconfig -a3. sudo route -nv4. sudo dh ...

  6. shell-code-5-函数

    # 函数必须在使用前定义 # 如果不写return,将以最后一条命令运行结果,作为返回值. return后跟数值n(0-255) myFistFunc(){ read a read b return ...

  7. BZOJ 2508: 简单题

    题目大意: 加入直线,删除直线,求点到所有直线的距离的平方和. 题解: 把点到直线的距离公式写出来,然后展开.维护六个值,计算一个二元的多项式的最小值. 对x和y分别求导,导数都为零时取到极值.然后解 ...

  8. IntelliJ IDEA 类和方法注释的生成以及Javadoc的简单使用记录

    idea,设置类注释和,方法注释的常见的设置方法(不同的版本设置方法有所偏差,简单记录一些目前自己在使用的方法,) 方法注释:在keyMap中搜索Fix doc comment ,后点击右键设置一个快 ...

  9. python-selenium使用send_keys()方法写中文报错的解决方法

    问题描述: 自动化操作页面,输入中文姓名: # coding=utf-8 url = "http://dealer.bitauto.com/50002218/zuidijia/" ...

  10. 九度oj 题目1201:二叉排序树

    题目描述: 输入一系列整数,建立二叉排序数,并进行前序,中序,后序遍历. 输入: 输入第一行包括一个整数n(1<=n<=100).    接下来的一行包括n个整数. 输出: 可能有多组测试 ...