[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 ...
随机推荐
- opencast的docker安装
在之前的从源安装和从包安装opencast,都遇到较多环境问题导致失败.所有采用docker安装. Dockers是有能力打包应用程序及其虚拟容器,可以在任何Linux服务器上运行的依赖性工具,这有助 ...
- perl学习之:字符串函数
一.打开.关闭文件 open的返回值用来确定打开文件的操作是否成功,当其成功时返回非零值,失败时返回零,因此可以如下判断: if (open(MYFILE, "myfile" ...
- sklearn 快速入门教程
1. 获取数据 1.1 导入sklearn数据集 sklearn中包含了大量的优质的数据集,在你学习机器学习的过程中,你可以通过使用这些数据集实现出不同的模型,从而提高你的动手实践能力,同时这个过程也 ...
- Java并发编程的艺术 记录(一)
模拟死锁 package com.gjjun.concurrent; /** * 模拟死锁,来源于<Java并发编程的艺术> * @Author gjjun * @Create 2018/ ...
- LeetCode(90) Subsets II
题目 Given a collection of integers that might contain duplicates, nums, return all possible subsets. ...
- “程序设计与算法训练”课程设计:“BP神经网络的实现”(C++类封装实现)
一 题目: 71 BP神经网络的实现: 利用C++语言实现BP神经网络, 并利用BP神经网络解决螨虫分类问题: 蠓虫分类问题:对两种蠓虫(A与B)进行鉴别,依据的资料是触角和翅膀的长度,已知了9支Af ...
- centos 部署 自定义(succes)
安装前先检查一下有没有安装好了的JDK,Tomcat,MySQL,不过一般都没有. 1.安装JDK 1.1 下载jdk,可以到官网查看不同版本的下载地址 wget --no-check-certifi ...
- SQLAlchemy常用操作
Models 只是配置和使用比较简单,因为他是Django自带的ORM框架,也正是因为是Django原生的,所以兼容性远远不如SQLAlchemy 真正算得上全面的ORM框架必然是我们的SQLAlch ...
- Selenium启动项参数设置
再Selenium中使用不同的Webdriver可能会有不一样的方法,有些相同的操作会得到不同的结果, 本文主要介绍的是Chrome()的使用方法. 其他的Webdriver可以参考官方文档 Chro ...
- Python开发:网络编程
Python 提供了两个级别访问的网络服务.: 低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法. 高级别的网络 ...