What is the best way to fake dependencies in Jest for each individual test?

Check out the complete minimal reproducible example

Let's take a look at the app below:

src/food.js

const Food = {
  carbs: "rice",
  veg: "green beans",
  type: "dinner"
};

export default Food;

src/food.js

import Food from "./food";

function formatMeal() {
  const { carbs, veg, type } = Food;

  if (type === "dinner") {
    return `Good evening. Dinner consists of ${veg} and ${carbs}. Delicious!`;
  } else if (type === "breakfast") {
    return `Good morning. Breakfast includes ${veg} and ${carbs}. Yum!`;
  } else {
    return "No meal for you!";
  }
}

export default function getMeal() {
  const meal = formatMeal();

  return meal;
}

Below is a test scenario I have set up:

tests_/meal_test.js

import getMeal from "../src/meal";

describe("meal tests", () => {
  beforeEach(() => {
    jest.resetModules();
  });

  it("should display dinner", () => {
    expect(getMeal()).toBe(
      "Good evening. Dinner consists of green beans and rice. Delicious!"
    );
  });

  it("should display breakfast (mocked)", () => {
    jest.doMock("../src/food", () => ({
      type: "breakfast",
      veg: "avocado",
      carbs: "toast"
    }));

    // prints out the newly mocked food!
    console.log(require("../src/food"));

    // ...however, we did not mock it in time, so this fails!
    expect(getMeal()).toBe("Good morning. Breakfast includes avocado and toast. Yum!");
  });
});

How can I effectively mock the Food object for each individual test? Essentially, I want the mock to only apply to the "should display breakfast (mocked)" test case.

I also prefer not to alter the application source code if possible (although considering having Food as a function that returns an object might be acceptable - still struggling to make that work as well)

Some methods I have attempted already:

  • Passing the Food object through getMeal + employing dependency injection into formatMeal
    • (the main objective here is to avoid passing around Food throughout the entire app)
  • Manual mocking + jest.mock() - there might be a solution within this approach, but managing the value and resetting it per test due to import time discrepancies has proven challenging
    • Using jest.mock() at the start could override it for every test case, and figuring out how to adjust or reset the value of Food for each test remains elusive.

Answer №1

Concise answer

To ensure proper mocking in Jest, use require to import modules within each test function after setting up the mocks.

it("should print breakfast (mocked)", () => {
    jest.doMock(...);
    const getMeal = require("../src/meal").default;

    ...
});

Alternatively,

To mock an object like Food, turn it into a function and utilize jest.mock at the module level.

import getMeal from "../src/meal";
import food from "../src/food";

jest.mock("../src/food");
food.mockReturnValue({ ... });

...

Detailed explanation

Jest documentation highlights the importance of placing your jest.mock calls in close proximity to the require/import statement for effective mocking:

Note: In order to mock properly, Jest needs jest.mock('moduleName') to be in the same scope as the require/import statement.

ES6 imports are resolved before test functions execute, making it necessary to declare mocks outside of the tests and prior to any imports. Jest's Babel plugin hoists jest.mock statements to precede any imports, ensuring their execution beforehand. Remember that jest.doMock is deliberately not hoisted.

If dealing with objects like in the example where direct mocking is challenging due to the module structure, consider refreshing the module for altered values if needed.

Option 1a: Testing without ES6 modules

While ES6 imports demand module-level scoping, traditional require allows calling from test scopes instead.

describe("meal tests", () => {
  beforeEach(() => {
    jest.resetModules();
  });

  it("should print dinner", () => {
    const getMeal = require("../src/meal").default;

    expect(getMeal()).toBe(
      "Good evening. Dinner is green beans and rice. Yum!"
    );
  });

  it("should print breakfast (mocked)", () => {
    jest.doMock("../src/food", () => ({
      type: "breakfast",
      veg: "avocado",
      carbs: "toast"
    }));

    const getMeal = require("../src/meal").default;

    // ...test functionality
    expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
  });
});

Option 1b: Dynamically reloading module

An alternative approach involves wrapping the target function.

Instead of

import getMeal from "../src/meal";

try this:

const getMeal = () => require("../src/meal").default();

Option 2: Flexibility through functional exports

If the mocked module were a function rather than an object literal, seamless mocking would be possible. Such mutable mocks facilitate test variations.

src/food.js

const Food = {
  carbs: "rice",
  veg: "green beans",
  type: "dinner"
};

export default function() { return Food; }

src/meal.js

import getFood from "./food";

function formatMeal() {
  const { carbs, veg, type } = getFood();

  if (type === "dinner") {
    return `Good evening. Dinner is ${veg} and ${carbs}. Yum!`;
  } else if (type === "breakfast") {
    return `Good morning. Breakfast is ${veg} and ${carbs}. Yum!`;
  } else {
    return "No soup for you!";
  }
}

export default function getMeal() {
  const meal = formatMeal();

  return meal;
}

__tests__/meal_test.js

import getMeal from "../src/meal";
import food from "../src/food";

jest.mock("../src/food");

const realFood = jest.requireActual("../src/food").default;    
food.mockImplementation(realFood);

describe("meal tests", () => {
  beforeEach(() => {
    jest.resetModules();
  });

  it("should print dinner", () => {
    expect(getMeal()).toBe(
      "Good evening. Dinner is green beans and rice. Yum!"
    );
  });

  it("should print breakfast (mocked)", () => {
    food.mockReturnValueOnce({ 
        type: "breakfast",
        veg: "avocado",
        carbs: "toast"
    });

    // ...modify expectations
    expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
  });
});

Additional strategies include segregating tests across modules or utilizing mutable objects instead of default exports for more adaptable mocking.

Answer №2

@anttix provided a top-notch answer, but I'd like to introduce another perspective that could prove valuable in different situations.

babel-plugin-rewire makes it possible to override import Food from "./food"; for testing purposes.

To begin, simply run yarn add babel-plugin-rewire

babel.config.js

const presets = [
  [
    "@babel/env",
    {
      targets: {
        node: 'current',
      },
    },
  ],
];

const plugins = [ 
  "babel-plugin-rewire"
];

module.exports = { presets, plugins };

meal_test.js

import getMeal from "../src/meal";
import Food from "../src/food";
import { __RewireAPI__ as RewireAPI } from "../src/meal";

describe("meal tests", () => {
  // beforeEach(() => {
  //   jest.resetModules();
  // });
  afterEach(() => {
    RewireAPI.__Rewire__('Food', Food)
  });

  it("should print dinner", () => {
    expect(getMeal()).toBe(
      "Good evening. Dinner is green beans and rice. Yum!"
    );
  });

  it("should print breakfast (mocked)", () => {
    const mockFood = {
      type: "breakfast",
      veg: "avocado",
      carbs: "toast"
    };
    RewireAPI.__Rewire__('Food', mockFood)

    expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!");
  });

  it("should print dinner #2", () => {
    expect(getMeal()).toBe(
      "Good evening. Dinner is green beans and rice. Yum!"
    );
  });
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the process for transforming an AJAX request's onreadystatechange into a promise?

During the process of making a javascript AJAX request, I initially utilized the traditional callback approach to call the callback function within the onreadystatechange and retrieve all the values of readyState. However, upon switching my callback funct ...

Issue with fading hover effect on links is not functioning

I recently implemented a fade link hover effect using the code snippet below: a:link {color:#333333; text-decoration: none; -o-transition:.5s; -ms-transition:.5s; -moz-transition:.5s; -webkit-transition:.5s; transition:.5s;} a:visited {color:#FF0033; ...

Ways to determine the height of a row within a flexbox

Is it possible to obtain the height of each row within a flexbox container using JavaScript? For instance, if there are 3 rows in the container, can I retrieve the height of the second row specifically? ...

Adding an image from JavaScript to a .pug file

I am a beginner in web development and I am currently trying to learn how to use JavaScript and .pug. I have created a variable named campgrounds with image files. router.get('/campgrounds', function (req, res) { var campgrounds = [ { ...

In order to properly execute the JavaScript code, it is essential to create a highly customized HTML layout from the ER

I am currently utilizing this resource to create a gallery of images on my Rails page. Here is the HTML code required to display the images: <a href="assets/gallery/ave.jpg" title="Ave" data-gallery> <img src="assets/gallery/ave_tb.jpg" alt="Av ...

Bootstrap modals are refusing to open

I have encountered an issue where the images are not displaying on my modal. I have tried changing both the images and the code itself, but being new to this, I am unsure of what exactly is causing the problem. Could it be a fault in the Javascript code or ...

What's Vue.js error message about unknown action type?

My store was created in store/user.js export const state = () => ({ user: {}, }); export const mutations = { }; export const actions = { AUTH ({commit},{email, password}){ console.log('email, password =', email, password) } }; ...

Creating a customized pagination feature in Angular 8 without relying on material design components

I am looking to implement pagination in my Angular 8 project without relying on any material library. The data I receive includes an array with 10 data rows, as well as the first page link, last page link, and total number of pages. Server Response: { ...

How can callback functions be used with jquery-templ?

I am currently utilizing the plugin jquery-tmpl. Is there a way to define a callback function that runs after a template is executed? In other words, I want to achieve something like this: <script id='itemTemplate' type='text/html'& ...

Troubleshooting Event.target Problem in Firefox 6

When working in Firefox 6, I encountered an issue while trying to identify the target element on which the event occurred. Instead of displaying the desired element, it was showing as undefined in the alert message. Utilizing the Firebug tool for debugging ...

What is the process of converting the timing from my stopwatch to a standard time format?

I am currently working on a stopwatch project where I log the time into an array. However, when I try to use Math.min(array) or Math.max(array), it returns NaN (not a number). The time format for the stopwatch is like 00:00:15.91 which is not recognized as ...

Web application error: Karma and Webpack cannot locate Angular controller

After following a blog post on setting up an Angular project with webpack, I used an associated boilerplate on Github. Although the app is functioning properly, Karma seems to have trouble finding the controller it needs to test. // karma.config.js modul ...

Error occurs when trying to map an array within an asynchronous function

Hey there, I have an array of objects with validation inside my async function (router.post()) and I need to map it before validating. Here is the approach I am taking: ingredients.map(({ingredient, quantity})=>{ if(ingredient.trim().length < 1 | ...

Place the image on the canvas

I currently have a canvas where I am able to add text layers and images from flickr. My goal is to enable users to upload an image to the canvas using the html input. For uploading images from flickr, I am using this code: $(".search form.image-search"). ...

Looking to dynamically display users added to an array in a table using Angular and JavaScript?

As a newcomer to javascript and angularjs, I recently attempted to build a table that would display all users in an array dynamically as new users are added through a form. However, each time I run my code, I encounter the error message "Fill out the entir ...

Refreshin the attached DOM to a directive without a page reload

Within a directive, I have implemented some text and a video tag in the code below: app.directive('ngAzuremediaplayer', function () { return { restrict: 'AE', priority: 10, link: function (scope, elem, attr ...

Refresh React Components on the Fly (Solr)

I am relatively new to ReactJS In my React class, I have a function that is rendering multiple items: (Sample) var app = app || {}; app.Results = React.createClass({ componentDidMount: function () { }, handleUpdateEvent: function(id) ...

Facing an obstacle in Angular as I am unable to view my data

How can I bind the model of my controller in the init function and see the data when the init is called? Index.html <!DOCTYPE html> <html ng-app="I-Sign"> <head> <meta http-equiv='X-UA-Compatible' content='IE=edge&apo ...

After updating to version 0.10.10, Visual Studio Code no longer recognizes JavaScript syntax highlighting

Can anyone help me with this issue? I have attached a screenshot for reference. Any assistance would be greatly appreciated. https://i.sstatic.net/3cjKc.png ...

Best method for removing CrosshairMove event listener in lightweight charts

As per the documentation, using unsubscribeCrosshairMove allows us to remove a handler that was previously added with subscribeCrosshairMove. Our objective is to use unsubscribe... to eliminate previous handlers before re-subscribing with subscribe... af ...