zoukankan      html  css  js  c++  java
  • [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};
    View Code

    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;
    View Code

    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(); });
  • 相关阅读:
    mysql 优化(包含sql语句的书写)
    tomcat优化
    MySQL——修改root密码的4种方法(以windows为例)
    实现窗口中的文档自动向上滚动,方便阅读
    处理文本框的鼠标事件,判断鼠标的状态
    通过给事件处理程序传递this参数,获取事件源对象的引用。单机提交按钮时在信息框中显示用户输入的字符。
    在标签的事件属性字符串中编写程序,检查用户输入的密码明文
    通过使用浏览器对象模型,输出当前浏览器窗口中打开的文档的URL信息,并将显示在窗口中。
    创建一个卡片对象,卡片上标有“名字”、“地址”和“电话”等信息。名片对象提供一个方法以输出这些信息。
    测试Array对象的sort方法的作用。将1985,1970,1999,1998,2000,1963这些年份按升序输出。
  • 原文地址:https://www.cnblogs.com/Answer1215/p/9905082.html
Copyright © 2011-2022 走看看