avatarRazvan L

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

7027

Abstract

ss="hljs-title function_">hello</span>(<span class="hljs-params"></span>) { <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello "</span> + <span class="hljs-variable language_">this</span>.<span class="hljs-property">name</span>; } }

<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = <span class="hljs-title class_">User</span>;</pre></div><h2 id="503f">Mocking an entire class</h2><p id="119d">Since ES6 classes are essentially constructor functions with some syntactic sugar, any mock for an ES6 class must be a function or an actual ES6 class (which is, again, another function), and can therefore be mocked using the <code>jest.fn()</code> function.</p><p id="bee7">To mock an entire class, we can use the <code>jest.mock()</code> function that takes as argument the path of the module we want to create a mock of, and a callback function that returns a mock created with <code>jest.fn()</code>.</p><div id="087c"><pre><span class="hljs-keyword">const</span> <span class="hljs-title class_">MyClass</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./myclass'</span>);

jest.<span class="hljs-title function_">mock</span>(<span class="hljs-string">'./myclass'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> jest.<span class="hljs-title function_">fn</span>(<span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> { <span class="hljs-comment">// ...</span> }; }); });</pre></div><p id="8686">As you can see in this example, if we mock the <code>User</code> class the following way and instantiate a new object using the string <code>Jack</code>, the value contained in the <code>name</code> property will actually be the one defined in the mock and not the one passed to the constructor.</p><div id="5d56"><pre><span class="hljs-keyword">const</span> <span class="hljs-title class_">User</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./user'</span>);

jest.<span class="hljs-title function_">mock</span>(<span class="hljs-string">'./user'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> jest.<span class="hljs-title function_">fn</span>(<span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> { <span class="hljs-attr">name</span>: <span class="hljs-string">'John'</span>, <span class="hljs-attr">hello</span>: jest.<span class="hljs-title function_">fn</span>() }; }); });

<span class="hljs-title function_">test</span>(<span class="hljs-string">'it should mock the User constructor'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(<span class="hljs-string">'Jack'</span>);

<span class="hljs-title function_">expect</span>(user.<span class="hljs-property">name</span>).<span class="hljs-title function_">toBe</span>(<span class="hljs-string">'John'</span>); });</pre></div><p id="ff84">Alternatively, we can also mock a class per test — similarly to functions — using the <code>mockImplementationOnce()</code> method as follows.</p><div id="336d"><pre><span class="hljs-keyword">const</span> <span class="hljs-title class_">User</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./user'</span>);

jest.<span class="hljs-title function_">mock</span>(<span class="hljs-string">'./user'</span>);

<span class="hljs-title function_">test</span>(<span class="hljs-string">'it should mock the User constructor'</span>, <span class="hljs-function">() =></span> { <span class="hljs-title class_">User</span>.<span class="hljs-title function_">mockImplementationOnce</span>(<span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> { <span class="hljs-attr">name</span>: <span class="hljs-string">'John'</span>, <span class="hljs-attr">hello</span>: jest.<span class="hljs-title function_">fn</span>() }; });

<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(<span class="hljs-string">'Jack'</span>);

<span class="hljs-title function_">expect</span>(user.<span class="hljs-property">name</span>).<span class="hljs-title function_">toBe</span>(<span class="hljs-string">'John'</span>); });</pre></div><h2 id="6924">Spying on a class method</h2><p id="4381">To test the behaviour of a class without changing its actual implementation, we can use the <code>jest.spyOn()</code> function that take as argument an object and a method to track the calls of, and returns a mock function.</p><div id="8696"><pre><span class="hljs-keyword">const</span> <span class="hljs-title class_">User</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./user'</span>);

<span class="hljs-title function_">test</span>(<span class="hljs-string">'it should return the string "Hello John"'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(<span class="hljs-string">'John'</span>);

<span class="hljs-keyword">const</span> mock = jest.<span class="hljs-title function_">spyOn</span>(<span class="hljs-title class_">User</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>, <span class="hljs-string">'hello'</span>);

user.<span class="hljs-title function_">hello</span>();

<span class="hljs-title function_">expect</span>(mock).<span class="hljs-title function_">toHaveReturnedWith</span>(<span class="hljs-string">"Hello John"</span>); });</pre></div><h2 id="8414">Mocking a class method</h2><p id="2373">To mock a specific class method without altering any other properties, we can use the <code>jest.spyOn()</code> function we've just seen and chain it to the <code>.mockImplementationOnce()</code> method.</p><div id="5cf5"><pre><span class="hljs-keyword">const</span> <span class="hljs-title class_">User</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./user'</span>);

<span class="hljs-title function_">test</span>(<span class="hljs-string">'it should return the string "Hello John"'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">new</span> <span class="hljs-title class_">User</span>(<span class="hljs-string">'John'</span>);

<span class="hljs-keyword">const</span> mock = jest .<span class="hljs-title function_">spyOn</span>(<span class="hljs-title class_">User</span>.<span class="hljs-property"><span class="hljs-keyword">prototype</span></span>, <span class="hljs-string">'hello'</span>) .<span class="hljs-title function_">mockImplementationOnce</span>(<span class

Options

="hljs-function">() =></span> { <span class="hljs-keyword">return</span> <span class="hljs-string">"Gutten Tag John"</span>; });

user.<span class="hljs-title function_">hello</span>();

<span class="hljs-title function_">expect</span>(mock).<span class="hljs-title function_">toHaveReturnedWith</span>(<span class="hljs-string">"Gutten Tag John"</span>); });</pre></div><h1 id="2e8b">Mocking a module</h1><p id="a39e">Creating a mock of an entire module is actually similar to creating a mock of a class, as it can be done directly within the callback function of the <code>jest.mock()</code> function.</p><div id="a5e0"><pre>jest.<span class="hljs-title function_">mock</span>(<span class="hljs-string">'module'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> jest.<span class="hljs-title function_">fn</span>(<span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> { <span class="hljs-comment">// ...</span> }; }); });</pre></div><p id="7150">Let’s consider the following module that instantiates a new database handler using the <code>Sequelize</code> class, and performs a database connection attempt using the <code>authenticate()</code> method of the database handler object.</p><div id="d135"><pre><span class="hljs-keyword">const</span> <span class="hljs-title class_">Sequelize</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'sequelize'</span>);

<span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">connect</span>(<span class="hljs-params">env</span>) { <span class="hljs-keyword">const</span> db = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Sequelize</span>(env.<span class="hljs-property">name</span>, env.<span class="hljs-property">user</span>, env.<span class="hljs-property">password</span>, { <span class="hljs-attr">host</span>: env.<span class="hljs-property">host</span>, <span class="hljs-attr">port</span>: env.<span class="hljs-property">port</span>, <span class="hljs-attr">dialect</span>: env.<span class="hljs-property">dialect</span>, <span class="hljs-attr">logging</span>: env.<span class="hljs-property">logging</span> });

<span class="hljs-keyword">await</span> db.<span class="hljs-title function_">authenticate</span>();

<span class="hljs-keyword">return</span> db; };

<span class="hljs-variable language_">module</span>.<span class="hljs-property">exports</span> = connect;</pre></div><p id="faea">To assert that the <code>connect</code> function invokes the <code>Sequelize</code> class and the <code>authenticate()</code> method—without actually performing a connection attempt—we can mock the <code>Sequelize</code> module the following way:</p><div id="406d"><pre><span class="hljs-keyword">const</span> <span class="hljs-title class_">Sequelize</span> = <span class="hljs-built_in">require</span>(<span class="hljs-string">'sequelize'</span>); <span class="hljs-keyword">const</span> connect = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./connect'</span>);

jest.<span class="hljs-title function_">mock</span>(<span class="hljs-string">'sequelize'</span>, <span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> jest.<span class="hljs-title function_">fn</span>(<span class="hljs-function">() =></span> { <span class="hljs-keyword">return</span> { <span class="hljs-attr">authenticate</span>: jest.<span class="hljs-title function_">fn</span>() }; }); });

<span class="hljs-title function_">afterEach</span>(<span class="hljs-function">() =></span> jest.<span class="hljs-title function_">clearAllMocks</span>());

<span class="hljs-keyword">const</span> env = { <span class="hljs-attr">name</span>: <span class="hljs-string">'database'</span>, <span class="hljs-attr">user</span>: <span class="hljs-string">'john'</span>, <span class="hljs-attr">password</span>: <span class="hljs-string">'hello'</span>, <span class="hljs-attr">host</span>: <span class="hljs-string">'127.0.0.1'</span>, <span class="hljs-attr">port</span>: <span class="hljs-number">3306</span>, <span class="hljs-attr">dialect</span>: <span class="hljs-string">'mysql'</span>, <span class="hljs-attr">logging</span>: <span class="hljs-literal">true</span> };

<span class="hljs-title function_">test</span>(<span class="hljs-string">'it should invoke the Sequelize constructor'</span>, <span class="hljs-keyword">async</span> () => { <span class="hljs-keyword">await</span> <span class="hljs-title function_">connect</span>(env);

<span class="hljs-title function_">expect</span>(<span class="hljs-title class_">Sequelize</span>).<span class="hljs-title function_">toHaveBeenCalledWith</span>(env.<span class="hljs-property">name</span>, env.<span class="hljs-property">user</span>, env.<span class="hljs-property">password</span>, { <span class="hljs-attr">host</span>: env.<span class="hljs-property">host</span>, <span class="hljs-attr">port</span>: env.<span class="hljs-property">port</span>, <span class="hljs-attr">dialect</span>: env.<span class="hljs-property">dialect</span>, <span class="hljs-attr">logging</span>: env.<span class="hljs-property">logging</span> }); });

<span class="hljs-title function_">test</span>(<span class="hljs-string">'it should invoke the authenticate handler'</span>, <span class="hljs-keyword">async</span> () => { <span class="hljs-keyword">const</span> db = <span class="hljs-keyword">await</span> <span class="hljs-title function_">connect</span>(env);

<span class="hljs-title function_">expect</span>(db.<span class="hljs-property">authenticate</span>).<span class="hljs-title function_">toHaveBeenCalledTimes</span>(<span class="hljs-number">1</span>); });</pre></div><h1 id="6660">Want to learn more?</h1><p id="b83a">Learn how to build real-world APIs in Node.js from the first line of code to the last line of documentation with the book <a href="https://learnbackend.dev/books/build-layered-microservices"><b>Build Layered Microservices</b></a><b> </b>available at <a href="https://learnbackend.dev/"><b>learnbackend.dev</b></a><b>.</b></p><p id="e54b"><i>More content at <a href="https://plainenglish.io/"><b>PlainEnglish.io</b></a>.</i></p><p id="e63e"><i>Sign up for our <a href="http://newsletter.plainenglish.io/"><b>free weekly newsletter</b></a>. Follow us on <a href="https://twitter.com/inPlainEngHQ"><b>Twitter</b></a></i>, <a href="https://www.linkedin.com/company/inplainenglish/"><b><i>LinkedIn</i></b></a><i>, <a href="https://www.youtube.com/channel/UCtipWUghju290NWcn8jhyAw"><b>YouTube</b></a>, and <a href="https://discord.gg/GtDtUAvyhW"><b>Discord</b></a><b>.</b></i></p><p id="f90f"><b><i>Interested in scaling your software startup</i></b><i>? Check out <a href="https://circuit.ooo?utm=publication-post-cta"><b>Circuit</b></a>.</i></p></article></body>

How to Mock Functions, Classes, and Modules With Jest

A mock is a special type of function that allows to temporarily override the implementation of a single function, class, or module, to give it another behaviour in order to test it in isolation from external dependencies It also allows you to track how many times it has been invoked, with what parameters, and what it returned.

Mocking a single function

Let’s consider the following proxy() function.

function proxy(data, callback) {
  return callback(data);
}

module.exports = proxy;

Testing the execution

To asset that the callback() function is actually invoked when the proxy() function is executed, we can:

  1. Create a mock of the callback() function using the jest.fn() function.
  2. Execute the proxy() function using the mockFn mock.
  3. Test if the mock has been called, how many it has been called, and with what parameters using the following self-descriptive matchers.
const proxy = require('./proxy');

test('it should invoke the callback function', () => {
  // (1)
  const mockFn = jest.fn();

  // (2)
  proxy('Hello World', mockFn);

  // (3)
  expect(mockFn).toHaveBeenCalled();
  expect(mockFn).toHaveBeenCalledTimes(1);
  expect(mockFn).toHaveBeenCalledWith('Hello World');
});

Overriding the implementation

To override the implementation of a function, and consequently its behaviour, we can define its new implementation within the jest.fn() function itself, and test its result using the toHaveReturnedWith() matcher.

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

test('it should return the data length', () => {
  const mockFn = jest.fn(data => data && data.length || 0);

  proxy('Hello World', mockFn);

  expect(mockFn).toHaveReturnedWith(12);
});

Overriding the returned value

To override the return value of a function instead of its implementation, we can use the mockReturnValue() function which will return the same value for each call.

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

test('it should return the data length', () => {
  const mockFn = jest.fn().mockReturnValue(20);
  
  proxy('Hello world', mockFn);

  expect(mockFn).toHaveReturnedWith(20);

  proxy('Bonjour le monde', mockFn);

  expect(mockFn).toHaveReturnedWith(20);
});

Or we can use the mockReturnValueOnce() function which will return a specific value for the next call only.

test('it should return different data lengths', () => {
  const mockFn = jest.fn()
    .mockReturnValueOnce(3)
    .mockReturnValueOnce(5);

  proxy('Hello', mockFn);

  expect(mockFn).toHaveReturnedWith(3);

  proxy('Hello', mockFn);

  expect(mockFn).toHaveReturnedWith(5);
});

Mocking a class

Let’s consider the following User class, whose constructor takes as argument a string representing a name, and implements a hello() function that returns a string containing the name property.

class User {
  constructor(name) {
    this.name = name;
  }

  hello() {
    return "Hello " + this.name;
  }
}

module.exports = User;

Mocking an entire class

Since ES6 classes are essentially constructor functions with some syntactic sugar, any mock for an ES6 class must be a function or an actual ES6 class (which is, again, another function), and can therefore be mocked using the jest.fn() function.

To mock an entire class, we can use the jest.mock() function that takes as argument the path of the module we want to create a mock of, and a callback function that returns a mock created with jest.fn().

const MyClass = require('./myclass');

jest.mock('./myclass', () => {
  return jest.fn(() => {
    return {
      // ...
    };
  });
});

As you can see in this example, if we mock the User class the following way and instantiate a new object using the string Jack, the value contained in the name property will actually be the one defined in the mock and not the one passed to the constructor.

const User = require('./user');

jest.mock('./user', () => {
  return jest.fn(() => {
    return {
      name: 'John',
      hello: jest.fn()
    };
  });
});

test('it should mock the User constructor', () => {
  const user = new User('Jack');

  expect(user.name).toBe('John');
});

Alternatively, we can also mock a class per test — similarly to functions — using the mockImplementationOnce() method as follows.

const User = require('./user');

jest.mock('./user');

test('it should mock the User constructor', () => {
  User.mockImplementationOnce(() => {
    return {
      name: 'John',
      hello: jest.fn()
    };
  });

  const user = new User('Jack');

  expect(user.name).toBe('John');
});

Spying on a class method

To test the behaviour of a class without changing its actual implementation, we can use the jest.spyOn() function that take as argument an object and a method to track the calls of, and returns a mock function.

const User = require('./user');

test('it should return the string "Hello John"', () => {
  const user = new User('John');

  const mock = jest.spyOn(User.prototype, 'hello');

  user.hello();

  expect(mock).toHaveReturnedWith("Hello John");
});

Mocking a class method

To mock a specific class method without altering any other properties, we can use the jest.spyOn() function we've just seen and chain it to the .mockImplementationOnce() method.

const User = require('./user');

test('it should return the string "Hello John"', () => {
  const user = new User('John');

  const mock = jest
    .spyOn(User.prototype, 'hello')
    .mockImplementationOnce(() => {
      return "Gutten Tag John";
    });

  user.hello();

  expect(mock).toHaveReturnedWith("Gutten Tag John");
});

Mocking a module

Creating a mock of an entire module is actually similar to creating a mock of a class, as it can be done directly within the callback function of the jest.mock() function.

jest.mock('module', () => {
  return jest.fn(() => {
    return {
      // ...
    };
  });
});

Let’s consider the following module that instantiates a new database handler using the Sequelize class, and performs a database connection attempt using the authenticate() method of the database handler object.

const Sequelize = require('sequelize');

async function connect(env) {
  const db = new Sequelize(env.name, env.user, env.password, {
    host: env.host,
    port: env.port,
    dialect: env.dialect,
    logging: env.logging
  });
  
  await db.authenticate();

  return db;
};

module.exports = connect;

To assert that the connect function invokes the Sequelize class and the authenticate() method—without actually performing a connection attempt—we can mock the Sequelize module the following way:

const Sequelize = require('sequelize');
const connect = require('./connect');

jest.mock('sequelize', () => {
  return jest.fn(() => {
    return {
      authenticate: jest.fn()
    };
  });
});

afterEach(() => jest.clearAllMocks());

const env = {
  name: 'database',
  user: 'john',
  password: 'hello',
  host: '127.0.0.1',
  port: 3306,
  dialect: 'mysql',
  logging: true
};

test('it should invoke the Sequelize constructor', async () => {
  await connect(env);

  expect(Sequelize).toHaveBeenCalledWith(env.name, env.user, env.password, {
    host: env.host,
    port: env.port,
    dialect: env.dialect,
    logging: env.logging
  });
});

test('it should invoke the authenticate handler', async () => {
  const db = await connect(env);

  expect(db.authenticate).toHaveBeenCalledTimes(1);
});

Want to learn more?

Learn how to build real-world APIs in Node.js from the first line of code to the last line of documentation with the book Build Layered Microservices available at learnbackend.dev.

More content at PlainEnglish.io.

Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.

Interested in scaling your software startup? Check out Circuit.

Jest
JavaScript
Mock
Software Testing
Unit Testing
Recommended from ReadMedium