0

I have a function that manipulates the DOM and there are 2 cases that I want to test. One of those cases involve mocking some document functions to check if they were called and other other involves checking the DOM to see if it was actually changed.

Probably relevant data:

Jest: ^27.0.6
jsdom: ^16.6.0

Here's my function:

// file.js
export const loadStyles = () => {
    const fontLink = document.createElement('link');
    fontLink.setAttribute('href', 'some_href_1');
    fontLink.setAttribute('rel', 'stylesheet');
    const iconLink = document.createElement('link');
    iconLink.setAttribute('href', 'some_href_2');
    iconLink.setAttribute('rel', 'stylesheet');

    const head = document.head;
    head.appendChild(fontLink);
    head.appendChild(iconLink);
};

The problem is that the mocks from test 1 are still persistent in test 2, even if the document is overwritten by a new instance of JSDOM. However, if I put test 2 before test 1, then the tests pass. Here's my test file:

import * as functions from './file.js';
import { JSDOM } from 'jsdom';

describe('Test Functions', () => {

    describe('loadStyles', () => {
        beforeEach(() => {
            jest.clearAllMocks();
            document = new JSDOM().window.document;
        });

        // test 1
        it('should create the style link attributes', () => {
            document.createElement = jest.fn().mockReturnValue({
                setAttribute: jest.fn()
            } as unknown as HTMLElement);
            document.head.appendChild = jest.fn();
            functions.loadStyles();

            expect(document.createElement).toHaveBeenCalledTimes(2);
            expect(document.createElement).toHaveBeenCalledWith('link');
            expect(document.createElement).toHaveBeenLastCalledWith('link');
            expect(document.head.appendChild).toHaveBeenCalledTimes(2);
        });

        // test 2
        it('should add the style links to the DOM', () => {
            functions.loadStyles();
            const linkElements = document.querySelectorAll<HTMLLinkElement>('link');
            expect(linkElements.length).toBe(2);
            const firstLink = linkElements.item(0);
            const secondLink = linkElements.item(1);

            expect(firstLink.href).toEqual('some_href_1');
            expect(firstLink.rel).toEqual('stylesheet');

            expect(secondLink.href).toEqual('some_href_2');
            expect(secondLink.rel).toEqual('stylesheet');
        });
    });
});

Ideally, I'd like to have a fresh instance of document in each it block. I thought that overwriting the document with new JSDOM().window.document would have worked but I guess it didn't. Thanks for the help in advance!

1 Answers1

0

So after some searching, I realised that I don't need JSDOM. The problem was that I was using jest.fn() for mocking the document functions. According to an answer here, it's better to use jest.spyOn for my case and call mockRestore() afterwards.

import * as functions from './file.js';

describe('Test Functions', () => {

    describe('loadStyles', () => {
        beforeEach(() => {
            jest.restoreAllMocks();
            document.body.innerHTML = '';
            document.head.innerHTML = '';
        });

        // test 1
        it('should create the style link attributes', () => {
            const createElementSpy = jest.spyOn(document, 'createElement');
            const appendChildSpy = jest.spyOn(document.head, 'appendChild');
            functions.loadStyles();

            expect(document.createElement).toHaveBeenCalledTimes(2);
            expect(document.createElement).toHaveBeenCalledWith('link');
            expect(document.createElement).toHaveBeenLastCalledWith('link');
            expect(document.head.appendChild).toHaveBeenCalledTimes(2);

            appendChildSpy.mockRestore();
            createElementSpy.mockRestore();
        });

        // test 2
        it('should add the style links to the DOM', () => {
            functions.loadStyles();
            const linkElements = document.querySelectorAll<HTMLLinkElement>('link');
            expect(linkElements.length).toBe(2);
            const firstLink = linkElements.item(0);
            const secondLink = linkElements.item(1);

            expect(firstLink.href).toEqual('some_href_1');
            expect(firstLink.rel).toEqual('stylesheet');

            expect(secondLink.href).toEqual('some_href_2');
            expect(secondLink.rel).toEqual('stylesheet');
        });
    });
});

Additionally, for future reference, JSDOM and Jest don't seem to play well together according to an answer here.