📕
Dan Fitz's Notes
  • README
  • Ai
    • Supervised Machine Learning
      • Introduction To Machine Learning
      • Regression With Multiple Input Variables
      • Classification
  • Csharp
    • C Sharp Advanced
      • Generics
      • Delegates
      • Lambda Expressions
      • Events
    • C Sharp Fundamentals
      • Intro To C
      • Primitive Types And Expressions
      • Non Primitive Types
      • Control Flow
      • Arrays And Lists
      • Working With Dates
      • Working With Text
      • Working With Files
      • Debugging Applications
    • C Sharp Intermediate
      • Classes
      • Association Between Classes
      • Inheritance
      • Polymorphism
      • Interfaces
  • Java
    • Inheritance Data Structures Java
      • Inheritance Polymorphism Using Overriding And Access Modifiers
      • Abstract Classes And Debugging
      • File I O And Exceptions
      • Collections Maps And Regular Expressions
    • Intro To Java
      • Introduction To Java Classes And Eclipse
      • Unit Testing Arrays And Array Lists
      • Static Variables Methods And Polymorphism Using Overloading
  • Javascript
    • Algorithms Data Structures
      • Big O Notation
      • Analyzing Performance Of Arrays And Objects
      • Problem Solving Approach
      • Problem Solving Patterns
      • Recursion
      • Searching Algorithms
      • Bubble Selection And Insertion Sort
      • Merge Sort
      • Quick Sort
      • Radix Sort
      • Data Structures Introduction
      • Singly Linked Lists
      • Doubly Linked Lists
      • Stacks And Queues
      • Binary Search Trees
      • Tree Traversal
      • Binary Heaps
    • Complete Nodejs
      • Understanding Node.js
      • REST AP Is And Mongoose
      • API Authentication And Security
      • Node.js Module System
      • File System And Command Line Args
      • Debugging Node.js
      • Asynchronous Node.js
      • Web Servers
      • Accessing API From Browser
      • Application Deployment
      • Mongo DB And Promises
    • Complete React Native
      • Working With Content
      • Building Lists
      • Navigating Users Between Screens
      • State Management
      • Handling Screen Layout
      • Setting Up An App
      • More On Navigation
      • Advanced Statement Management With Context
      • Building A Custom Express API
      • In App Authentication
    • Epic React
      • React Fundamentals
      • React Hooks
      • Advanced React Hooks
      • Advanced React Patterns
      • React Performance
    • Fireship Firestore
      • Firestore Queries And Data Modeling Course
      • Model Relational Data In Firestore No SQL
    • Functional Light Javascript
      • Intro
      • Function Purity
      • Argument Adapters
      • Point Free
      • Closure
      • Composition
      • Immutability
      • Recursion
      • List Operations
      • Transduction
      • Data Structure Operations
      • Async
    • Js Weird Parts
      • Execution Contexts And Lexical Environments
      • Types And Operators
      • Objects And Functions
      • Object Oriented Java Script And Prototypal Inheritance
      • Defining Objects
    • Mastering Chrome Dev Tools
      • Introduction
      • Editing
      • Debugging
      • Networking
      • Auditing
      • Node.js Profiling
      • Performance Monitoring
      • Image Performance
      • Memory
    • React Complete Guide
      • What Is React
      • React Basics
      • Rendering Lists And Conditionals
      • Styling React Components
      • Debugging React Apps
      • Component Deep Dive
      • Building A React App
      • Reaching Out To The Web
      • Routing
    • React Testing
      • Intro To Jest Enzyme And TDD
      • Basic Testing
      • Redux Testing
      • Redux Thunk Testing
    • Serverless Bootcamp
      • Introduction
      • Auction Service Setup
      • Auction Service CRUD Operations
      • Auction Service Processing Auctions
    • Testing Javascript
      • Fundamentals Of Testing
      • Static Analysis Testing
      • Mocking Fundamentals
      • Configuring Jest
      • Test React Components With Jest And React Testing Library
    • Typescript Developers Guide
      • Getting Started With Type Script
      • What Is A Type System
      • Type Annotations In Action
      • Annotations With Functions And Objects
      • Mastering Typed Arrays
      • Tuples In Type Script
      • The All Important Interface
      • Building Functionality With Classes
    • Web Performance With Webpack
      • Intro
      • Code Splitting
      • Module Methods Magic Comments
  • Other
    • Algo Expert
      • Defining Data Structures And Complexity Analysis
      • Memory
      • Big O Notation
      • Logarithm
      • Arrays
      • Linked Lists
      • Hash Tables
      • Stacks And Queues
      • Strings
      • Graphs
      • Trees
    • Aws Solutions Architect
      • AWS Fundamentals IAM EC 2
    • Fundamentals Math
      • Numbers And Negative Numbers
      • Factors And Multiples
      • Fractions
    • Mysql Bootcamp
      • Overview And Installation
      • Creating Databases And Tables
      • Inserting Data
      • CRUD Commands
      • The World Of String Functions
      • Refining Our Selections
      • The Magic Of Aggregate Functions
    • Random Notes
      • Understanding React Hooks
  • Python
    • Data Analysis Using Python
      • Loading Querying And Filtering Data Using The Csv Module
      • Loading Querying Joining And Filtering Data Using Pandas
      • Summarizing And Visualizing Data
    • Intro To Python
      • Course Introduction Intro To Programming And The Python Language Variables Conditionals Jupyter Notebook And IDLE
      • Intro To Lists Loops And Functions
      • More With Lists Strings Tuples Sets And Py Charm
      • Dictionaries And Files
Powered by GitBook
On this page
  • What is Mocking?
  • Monkey-patching: Overriding Object Properties
  • Creating a Mock Factory Function
  • Jest version
  • Our version
  • Upgrading Our Monkey-patch with spyOn
  • Jest version
  • Our version
  • Mocking a Module
  • Jest version
  • Our version
  • Sharing Mocked Modules
  • Jest version
  • Our version
  1. Javascript
  2. Testing Javascript

Mocking Fundamentals

This section implements mocking in vanilla JS without help from testing frameworks and helper functions like Jest. The goal is to better understand how mocking works behind the scenes.

What is Mocking?

The idea behind mocking comes when you have some module that is too expensive to use directly. For example, there could be asynchronous functions involved, or it could be processing a credit card payment, which is too expensive to actually test.

So, you create a fake or mock version of the module, so you can test the module without incurring the costs.

Monkey-patching: Overriding Object Properties

The process of monkey-patching is to

  1. Override the object method with your own mock method.

  2. Cleanup that mock method by reassigning the key to the original method after you're done your test. (This is so other tests aren't affected by what happens in the target test.)

In the example below, we have a utils object with helper functions that we need to monkey-patch:

const assert = require('assert');
const thumbWar = require('../thumb-war');
const utils = require('../utils');

// Override/monkey patch
const originalGetWinner = utils.getWinner; // store original
utils.getWinner = (p1, p2) => p1; // this method is now deterministic for easier testing

const winner = thumbWar('Kent C. Dodds', 'Ken Wheeler');
assert.strictEqual(winner, 'Kent C. Dodds');

// Cleanup
utils.getWinner = originalGetWinner;

Creating a Mock Factory Function

Jest version

A mock function is a special function that stores properties as it's called, making the function easier to test. We will be recreating some of the functionality of jest.fn.

In Jest, here's some cool things you can do:

utils.getWinner = jest.fn((p1, p2) => p1); // custom implementation

const winner = thumbWar('Kent C. Dodds', 'Ken Wheeler');

// You can test number of times called
expect(utils.getWinner).toHaveBeenCalledTimes(2);
// You can test arguments passed in calls
expect(utils.getWinner.mock.calls).toEqual([
  ['Dan', 'John'],
  ['Dan', 'John'],
]);

Our version

Here's a bare-bones mock factory function. It creates a mock function that utilizes a user-provided implementation of that function. That implementation replaces the original implementation:

const fn = implementation => {
  const mockFn = (...args) => {
    return implementation(...args);
  };
  return mockFn;
};

We want to add the following features to our mock function:

  • Stores number of times it's been called.

  • Stores the arguments passed into each call.

const fn = implementation => {
  const mockFn = (...args) => {
    mockFn.numCalls++;
    mockFn.mockFn.mock.calls.push(args);
    return implementation(...args);
  };

  // Initialize starting values
  mockFn.numCalls = 0;
  mockFn.mock = { calls: [] };

  return mockFn;
};

Now we can perform tests to make sure our mock function ran as many times as we expected and with the arguments we expected.

const originalWinner = utils.getWinner;
utils.getWinner = fn((p1, p2) => p1); // deterministic

utils.getWinner('Dan', 'John');
utils.getWinner('Dan', 'John');

expect(utils.getWinner.numCalls).toBe(2);
expect(utils.getWinner.mocks.calls).toEqual([
  ['Dan', 'John'],
  ['Dan', 'John'],
]);

// Cleanup
utils.getWinner = originalWinner;

Upgrading Our Monkey-patch with spyOn

Our monkey-patch solution of storing originalWinner and restoring it during cleanup is a bit messy. We can instead use spyOn and mockRestore to do that for us.

Jest version

In Jest, here's the code:

// Overrides original function with mock function at 'getWinner' key
jest.spyOn(utils, 'getWinner');
// Adds custom implementation to pre-existing mock function (instead of during initialization)
utils.getWinner.mockImplementation((p1, p2) => p1);

// Run tests...

// Cleanup
utils.getWinner.mockRestore();

Our version

To recreate the above functionality, here are the requirements:

  1. spyOn needs to override the original function with a mock function generated by fn.

    • Note: fn needs to accept zero arguments here because the custom implementation comes after.

  2. We need a mockRestore method that resets the function back to its original state.

  3. We need a mockImplementation method that updates and/or sets the custom implementation.

Note: Our solution below uses closures to solve 2 and 3.

// Default empty function is what allow fn() call with no arguments
function fn(impl = () => {}) {
  const mockFn = (...args) => {
    mockFn.mock.calls.push(args);
    return impl(...args);
  };
  mockFn.mock = { calls: [] };
  // Using closure, we can update our implementation to solve 3
  mockFn.mockImplementation = newImpl => (impl = newImpl);
  return mockFn;
}

function spyOn(object, property) {
  // By storing original implementation, we can override with a mock function, solving 1
  const originalImpl = object[property];
  object[property] = fn();

  // Then using closure, we can restore to the original implementation to solve 2
  object[property].mockRestore = () => {
    object[property] = originalImpl;
  };
}

Mocking a Module

As it turns out, monkey-patching only works with CommonJS. It doesn't work for ES6 modules.

// CommonJS import
const utils = require('../utils');

// ES6 module import
import utils from '../utils';

Jest version

To solve this, Jest introduces the mock method. It has 2 parameters:

  1. Relative path to module, and

  2. Module factory function that returns a mocked version of the module.

import utils from '../utils';

// Overrides module
jest.mock('../utils', () => {
  return {
    getWinner: jest.fn((p1, p2) => p1),
  };
});

// Run tests...

// Cleans up metadata in mock function
utils.getWinner.mockReset();

Our version

The way that Jest kind of implements mock behind the scenes is to utilize require.cache, an object that stores information about modules in node. Then when you import that module into your file, it will import the mock version instead.

The goal in our vanilla version is to basically

  1. Manually inject our mock module into require.cache, making sure to include our mock functions as well.

  2. Delete the mock module from require.cache after we're done testing.

const mock = (relativePath, factory) => {
  const resolvedPath = require.resolve(relativePath);
  require.cache[resolvedPath] = {
    id: resolvedPath,
    filename: resolvedPath,
    loaded: true,
    exports: factory(),
  };

  return () => {
    delete require.cache[resolvedPath];
  };
};

const cleanup = mock('../utils', () => {
  return {
    getWinner: fn((p1, p2) => p1),
  };
});

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

// Run tests...

cleanup();

Things to note:

  • require.resolve gets the path for the file.

  • We return a cleanup function to remove the mocked module.

  • The mocked module needs to be created before importing it into the file.

Pro tip: Jest actually will run mock before any other line of code when running your tests for you. This is a convenience it provides, which is especially useful given that ES modules are always hoisted to the top of the file.

Sharing Mocked Modules

Very often, you use a module in multiple test files. In this case, you want to externalize your mocked module and re-use it across tests.

Jest version

Jest allows you to share your mocked module by accessing a __mocks__ directory at the same directory level as your module file.

.
+-- utils.js
+-- __mocks__
| +--utils.js

When you call jest.mock, it will pull your module from the __mocks__ directory instead of the original file.

// In test file
// Notice that we don't need to pass a mock module factory function
jest.mock('../utils');

// In __mocks__/utils.js
module.exports = {
  getWinner: jest.fn((p1, p2) => p1),
};

Our version

To implement this ourselves, we need to do the following in our test:

  1. Cache the mocked module by running require. This will make it show up in require.cache.

  2. Add the mocked module to require.cache at the real module's path.

  3. Finally, when we require our module, it will import the mocked module instead.

// 1. Cache
require('../__mock__/utils');
// 2. Replace
const mockPath = require.resolve('../__mock__/utils');
const utilsPath = require.resolve('../utils');
require.cache[utilsPath] = require.cache[mockPath];

// 3. Import
const utils = require('../utils');

And that's it! (Of course, this is a huge simplification of what Jest is actually doing. Jest takes full control of the module system.)

PreviousStatic Analysis TestingNextConfiguring Jest

Last updated 3 years ago