Using Spies and Mocks on complex objects with Jest

Tags: , , , ,



I’m fairly new to testing and writing tests for a currently uncovered javaScript codebase using Jest. The code covers some niche use cases, as its conditionally injected and executed by the browser during page load. Anyway, I’m having issues mocking up custom objects. Here’s the function in question:

const setEnterpriseCookie = () => {
        // Get the current page uri
        let path = window.location.pathname;

        // Matches all pages containing '/regex_expression'
        if (path.match(/.*/regex_expression.*/)) {
            window.TOOL.cookie.setCookie(...args);
        }
    };

As far as I understand, I need to mock both window.location.pathname to return a string, and I need to mock window.TOOL.cookie.setCookie() as a mock function. Here’s my attempt at the test:

var windowSpy;

describe('Tests for the page-specific-methods.js file', () => {

    beforeEach( () => {
        windowSpy = jest.spyOn(global, 'window', 'get');
    });

    afterEach( () => {
        windowSpy.mockRestore();
    })

    test('Test the page path detecting the enterprise string', () => {
        windowSpy.mockImplementation( () => ({
            location: {
                pathname: '/enterprise/contact',
            },
            TOOL: {
                cookie: {
                    setCookie: jest.fn(),
                },
            },
        }));

        setEnterpriseCookie();
        
        expect(window.TOOL.cookie.setCookie).toBeCalledTimes(1);
        expect(window.TOOL.cookie.setCookie).toHaveBeenLastCalledWith(...args);
    })
});

The test fails, saying that window.TOOL.cookie.setCookie was called 0 times. I’ve dug into the process, and found that window.location.pathname is executing as expected, and thus the code is entering the conditional that calls window.TOOL.cookie.setCookie. I think the problem is somewhere in how I’m mocking window.TOOL.cookie.setCookie, but I haven’t been able to find any help that describes how to mock methods so many .’s deep.

Thanks in advance for the help!

Answer

Just use Object.defineProperty() defines property directly on window object.

E.g.

index.js:

const setEnterpriseCookie = (...args) => {
  let path = window.location.pathname;
  if (path.match(/.*/enterprise.*/)) {
    window.TOOL.cookie.setCookie(...args);
  }
};

exports.setEnterpriseCookie = setEnterpriseCookie;

index.test.js:

const { setEnterpriseCookie } = require('./');

describe('63274598', () => {
  describe('Tests for the page-specific-methods.js file', () => {
    test('Test the page path detecting the enterprise string', () => {
      Object.defineProperty(window, 'location', {
        value: { pathname: '/enterprise/contact' },
      });
      Object.defineProperty(window, 'TOOL', {
        value: {
          cookie: {
            setCookie: jest.fn(),
          },
        },
      });
      setEnterpriseCookie('123');

      expect(window.TOOL.cookie.setCookie).toBeCalledTimes(1);
      expect(window.TOOL.cookie.setCookie).toHaveBeenLastCalledWith('123');
    });
  });
});

unit test result:

 PASS  stackoverflow/63274598/index.test.js (13.923s)
  63274598
    Tests for the page-specific-methods.js file
      ✓ Test the page path detecting the enterprise string (4ms)

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |       50 |     100 |     100 |                   
 index.js |     100 |       50 |     100 |     100 | 3                 
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        15.975s


Source: stackoverflow