[Testing] JavaScript Mocking Fundamentals
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的更多相关文章
- Googletest - Google Testing and Mocking Framework
Googletest - Google Testing and Mocking Framework https://github.com/google/googletest
- [Testing] Static Analysis Testing JavaScript Applications
The static code analysis and linting tool ESLint is the de-facto standard for linting JavaScript pro ...
- 12款简化 Web 开发的 JavaScript 开发框架
前端框架简化了开发过程中,像 Bootstrap 和 Foundation 就是前端框架的佼佼者.在这篇文章了,我们编制了一组新鲜的,实用的,可以帮助您建立高质量的 Web 应用程序的 JavaScr ...
- JavaScript测试工具比较: QUnit, Jasmine, and Mocha
1. QUnit A JavaScript Unit Testing framework. QUnit is a powerful, easy-to-use JavaScript unit testi ...
- 15款加速 Web 开发的 JavaScript 框架
JavaScript 可以通过多种方式来创建交互式的网站和 Web 应用程序.利用 JavaScript,可以让你移动 HTML 元素,创建各种各样的自定义动画,给你的访问者更好的终端用户体验. 对于 ...
- 关于JavaScript测试工具:QUnit, Jasmine, MoCha
在进行前端开发过程中,在某些场景下,需要通过编写单元测试来提高代码质量.而JavaScript常用的单元测试框架有这几个:QUnit, Jasmine, MoCha.下面就基于这三个工具,简单做一比较 ...
- 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 ...
- 近期流行的JavaScript框架与主题
[新年快乐]2017年你应该关注的JavaScript框架与主题 2017-01-01 王下邀月熊 JavaScript JavaScript的繁荣促生了很多优秀的技术.框架与工具库,这空前的繁荣也给 ...
- JavaScript引擎基本原理: 优化prototypes
原文链接: JavaScript engine fundamentals: optimizing prototypes 这篇文章介绍了一些JavaScript引擎常用的优化关键点, 并不只是Bened ...
随机推荐
- grafana绘制图表
安装方法 系统为ubuntu16 1首先添加以下到/etc/apt/sources.list: deb https://packagecloud.io/grafana/stable/debian/ s ...
- docker系列之安装配置
由于docker支持内核3.8以上的,所以我们要安装centos7系统,这个系统的内核是3.10,来支持docker使用环境 一.用UItralSo制作u盘启动 打开找到要做u盘启动的镜像 二.安装C ...
- 如何用纯 CSS 创作一个记事本翻页动画
效果预览 在线演示 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/qKOPGw 可交互视频教 ...
- 设计模式之建造者模式——Builder
一.概述 Builder模式,中文名为建造者模式,又名生成器模式.构建者模式等,是创建型设计模式之一.用于将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示. 1.适用性: 对象 ...
- java null 空指针
对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常(NPE)的骚扰.连Java的发明者都承认这是他的一项巨大失误.Java为什么要保留null呢?null出现有一段时间了,并且我认 ...
- CSS布局基础--BFC
1,什么是BFC BFC(Block Formatting Context)块级格式化上下文,它就是一个环境,HTML元素在这个环境中按照一定规则进行布局.一个环境中的元素不会影响到其他环境中的布局. ...
- linux 安装SNV服务
1.安装vnc server[root@pxe ~]# yum install tigervnc-server -y 2.设置 vnc server 开机启动[root@pxe ~]# chkconf ...
- 【01】blockqote美化
[01]blockqote美化 <!DOCTYPE html> <html lang="zh-cn"> <head> <meta ch ...
- 【C#】穿马甲的流程控制语句
导读:话说当年选择.顺序.循环语句风靡整个VB,今年发现,那几个东西又换了件衣服,跑到了C#里蹦跶.开始,真被这几个穿马甲的吓了一跳,没看出来这是老伙伴.突然有一天,瞥见了脱下新衣的孩子们.哈哈哈哈. ...
- FZU 2020 :组合 【lucas】
Problem Description 给出组合数C(n,m), 表示从n个元素中选出m个元素的方案数.例如C(5,2) = 10, C(4,2) = 6.可是当n,m比较大的时候,C(n,m)很大! ...