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. 自学入门 Python 优质中文资源索引

    所有资源基于 Python3 版本,全部中文内容,适用于 爬虫 / Web / 数据 方向,每个单元根据学习习惯从 书籍 / 文档 / 视频 中选择一类即可,建议任选一本书籍,然后配合文档类进行学习. ...

  2. 面试准备——java设计模式

    1 总体来说,设计模式分为三大类: 设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案. 创建型模式(五种):工厂方法模式.抽象工厂模式.单例模式. ...

  3. Linux rm删除

    将 test1子目录及子目录中所有档案删除 命令: rm -r test1 rm -rf test2命令会将 test2 子目录及子目录中所有档案删除,并且不用一一确认 命令: rm -rf  tes ...

  4. [android篇]声明权限

    要实施您自己的权限,必须先使用一个或多个 <permission> 元素在 AndroidManifest.xml 中声明它们. 实际上,在开发过程中,当我们使用了某些系统特性的功能,且此 ...

  5. shit layui & bugs

    shit layui & bugs use is not useful at all! http://www.layui.com/demo/form.html layui.use([" ...

  6. BZOJ 4650 [Noi2016]优秀的拆分 ——后缀数组

    我们只需要统计在某一个点开始的形如$AA$字符串个数,和结束的个数相乘求和. 首先枚举循环节的长度L.即$\mid (A) \mid=L$ 然后肯定会经过s[i]和[i+L]至少两个点. 然后我们可以 ...

  7. P1681 最大正方形II (动态规划)

    题目背景 忙完了学校的事,v神终于可以做他的"正事":陪女朋友散步.一天,他和女朋友走着走着,不知不觉就来到了一个千里无烟的地方.v神正要往回走,如发现了一块牌子,牌子上有有一行小 ...

  8. 【离散化树状数组】Nordic Collegiate Programming Contest G.Galactic Collegiate Programming Contest

    #include<bits/stdc++.h> using namespace std; typedef long long ll; int n,m; ; struct node { in ...

  9. 【loj6029】「雅礼集训 2017 Day1」市场

    题目 题意:四种操作,区间加法.区间除法(下取整).区间求最小值.区间求和. 第1.3.4个操作都是摆设,关键在于如何做区间除法. 很明显不能直接把区间的和做除法后向下取整,因为区间和可能会多凑出一个 ...

  10. 【2018.4.5】Shoi2017题集

    这三道题分别对应bzoj4868~4870,pdf没法往这放,因此放弃了. T1: 方法1(正解):三分法 考虑暴力枚举最晚公布的时间x,关注到2操作是没有负面影响的1操作,所以如果A大于B,那么只需 ...