Mocking `document` in jest
Asked Answered
H

10

86

I'm trying to write tests for my web components projects in jest. I already use babel with es2015 preset. I'm facing an issue while loading the js file. I have followed a piece of code where document object has a currentScript object. But in test context it is null. So I was thinking of mocking same. But jest.fn() is not really help in same. How can I handle this issue?

Piece of code where jest is failing.

var currentScriptElement = document._currentScript || document.currentScript;
var importDoc = currentScriptElement.ownerDocument;

Test case I have written. component.test.js

import * as Component from './sample-component.js';

describe('component test', function() {
  it('check instance', function() {
    console.log(Component);
    expect(Component).toBeDefined();
  });
});

Following is the error thrown by jest

Test suite failed to run

    TypeError: Cannot read property 'ownerDocument' of null

      at src/components/sample-component/sample-component.js:4:39

Update: As per suggestion from Andreas Köberle, I have added some global vars and tried to mock like following

__DEV__.document.currentScript = document._currentScript = {
  ownerDocument: ''
};
__DEV__.window = {
  document: __DEV__.document
}
__DEV__.document.registerElement = jest.fn();

import * as Component from './arc-sample-component.js';

describe('component test', function() {
  it('check instance', function() {
    console.log(Component);
    expect(Component).toBeDefined();
  });
});

But no luck

Update: I have tried above code without __dev__. Also by setting document as global.

Hedgerow answered 12/12, 2016 at 9:47 Comment(4)
Have you tried using global.document?Mattos
yes..i have tried that..no luck..Hedgerow
So I basically used jsdom like const jsdom = require('jsdom'); const documentHTML = '<!doctype html><html><body><div id="root"></div></body></html>'; global.document = jsdom.jsdom(documentHTML); And after this I tack on anything I want to the document and its available in my tests.Mattos
actually problem here is, jsdom is very simple and doesn't have webcomponents API. Anyways temporarily resolved it as per my answer.Hedgerow
B
57

Similar to what others have said, but instead of trying to mock the DOM yourself, just use JSDOM:

// __mocks__/client.js

import { JSDOM } from "jsdom"
const dom = new JSDOM()
global.document = dom.window.document
global.window = dom.window

Then in your jest config:

    "setupFiles": [
      "./__mocks__/client.js"
    ],
Basra answered 31/5, 2018 at 17:32 Comment(6)
TypeError: _jsdom.JSDOM is not a constructorHasheem
they must have changed their API since a year ago?Basra
looks like new JSDOM() is still correct - maybe you have something else configured incorrectly, like babel? github.com/jsdom/jsdomBasra
they don't put changelogs with releases so hard to tell github.com/jsdom/jsdom/releasesBasra
The changelog is at github.com/jsdom/jsdom/blob/master/Changelog.mdJermayne
Yes you need to have babel-jest and @babel/plugin-transform-modules-commonjs. It still works with Jest 24 (I use ts-jest) and is great to mock other global variables like window.navigator or navigator :-)Edeline
H
25

I have resolved this using setUpFiles property in jest. This will execute after jsdom and before each test which is perfect for me.

Set setupFiles, in Jest config, e.g.:

"setupFiles": ["<rootDir>/browserMock.js"]


// browserMock.js
Object.defineProperty(document, 'currentScript', {
  value: document.createElement('script'),
});

Ideal situation would be loading webcomponents.js to polyfill the jsdom.

Hedgerow answered 16/12, 2016 at 14:18 Comment(0)
U
20

I have been struggling with mocking document for a project I am on. I am calling document.querySelector() inside a React component and need to make sure it is working right. Ultimately this is what worked for me:

it('should test something', () => {
  const spyFunc = jest.fn();
  Object.defineProperty(global.document, 'querySelector', { value: spyFunc });
  <run some test>
  expect(spyFunc).toHaveBeenCalled()
});
Untwine answered 27/9, 2018 at 17:24 Comment(0)
B
7

If like me you're looking to mock document to undefined (e.g. for server side / client side tests) I was able to use object.defineProperty inside my test suites without having to use setupFiles

Example:

beforeAll(() => {
  Object.defineProperty(global, 'document', {});
})
Buggs answered 20/2, 2018 at 10:49 Comment(5)
I tried to mock document.referrer and document.URL by providing them in the object...no luck. The properties still showed up as their usual values in test runner.Bough
@Bough I've noticed this solution working in some versions of Node and not others. Maybe install NVM and have a play with different versionsBuggs
Thanks for the tip! I'll play with Node version and post some findings! I really like this strategy better than using the Jest config. I work on a big team, and it's better to keep our unit tests isolated and, of course atomic!Bough
Confirming -- The app where I tried this is using Node v8.11.1 and Jest 21.2.1Bough
TypeError: Cannot redefine property: document => ` at JSDOMEnvironment.teardown (node_modules/jest-environment-jsdom/build/index.js:192:14)`Iminourea
B
6

If you need to define test values for properties, there is a slightly more granular approach. Each property needs to be defined individually, and it's also necessary to make the properties writeable:

Object.defineProperty(window.document, 'URL', {
  writable: true,
  value: 'someurl'
});

See: https://github.com/facebook/jest/issues/890

This worked for me using Jest 21.2.1 and Node v8.11.1

Bough answered 18/5, 2018 at 22:49 Comment(0)
H
6

This is the structure in my project named super-project inside the folder super-project:


  • super-project
    • config
      • __mocks__
        • dom.js
    • src
      • user.js
    • tests
      • user.test.js
    • jest.config.js
    • package.json

You need to setup Jest to use a mock in your tests:

dom.js:

import { JSDOM } from "jsdom"
const dom = new JSDOM()
global.document = dom.window.document
global.window = dom.window

user.js:

export function create() {
  return document.createElement('table');  
}

user.test.js:

import { create } from "../src/user";

test('create table', () => {
  expect(create().outerHTML).toBe('<table></table>');
});

jest.config.js:

module.exports = {
  setupFiles: ["./config/__mocks__/dom.js"],
};

References:

You need to create a manual mock:
https://jestjs.io/docs/en/manual-mocks.html

Manipulating DOM object:
https://jestjs.io/docs/en/tutorial-jquery

Jest Configuration:
https://jestjs.io/docs/en/configuration

Hudnut answered 17/7, 2020 at 18:10 Comment(0)
C
3

I could resolve this same issue using global scope module on nodejs, setting document with mock of document, in my case, getElementsByClassName:

// My simple mock file
export default {
    getElementsByClassName: () => {
        return [{
            className: 'welcome'
        }]
    }
};

// Your test file
import document from './name.component.mock.js';
global.document = {
    getElementsByClassName: document.getElementsByClassName
};
Calida answered 28/1, 2017 at 19:38 Comment(2)
yep..that does but its very simple usecase...in webcomponents.js we have large set of API interconnected. Mocking them is one hell of a task...Hedgerow
But in your case, what you need mock exactly?Calida
I
2

I found another solution. Let's say inside of your component you want to get a reference to an element in the DOM by className (document.getElementsByClassName). You could do the following:

let wrapper
beforeEach(() => {
    wrapper = mount(<YourComponent/>)
    jest.spyOn(document, 'getElementsByClassName').mockImplementation(() => 
        [wrapper.find('.some-class').getDOMNode()]
    )
})

This way you are manually setting the return value of getElementsByClassName equal to the reference of .some-class. It might be necessary to rerender the component by calling wrapper.setProps({}).

Hope this helps some of you!

Indaba answered 17/12, 2020 at 10:34 Comment(0)
T
0
    Object.defineProperty(global.document, 'visibilityState', {
        configurable: true,
        writable: true,
        value: {
            get: jest.fn().mockResolvedValue('visible'),
        },
    });
Thaothapa answered 12/12, 2023 at 9:51 Comment(2)
Please provide some context, code only answers are rarely usefulPunctate
Please read How to Answer and take the tour.Iberian
E
-2

Hope this helps

const wrapper = document.createElement('div');
const render = shallow(<MockComponent{...props} />);
document.getElementById = jest.fn((id) => {
      wrapper.innerHTML = render.find(`#${id}`).html();
      return wrapper;
    });
Edyth answered 2/1, 2020 at 10:34 Comment(1)
This answer assumes the Component is a React Component. I don't see anything that indicates this. It looks like a plain javascript web component to me.Mcglone

© 2022 - 2025 — McMap. All rights reserved.