13

I want to write a test for a utils method. In that method I get a html element by id and then change the color of the element. The problem is that element is only available after a button click. How can I mock the element?

UtilListItem.js

import variables from '../stylesheets/Variables.scss';

export function activeListItem(props){
 let listItem = document.getElementById(props.id);
 listItem.style.backgroundColor = variables.whiteGray;
 return listItem;
}

UtilListeItem.test.js

it('check if the correct color is set for the acitve list item', () => {
  let props = {id:'123'}
  const listItem = activeListItem(props);
  expect(listItem.style.backgroundColor).toBe('#ededed');
});

error

TypeError: Cannot read property 'style' of null
WeSt
  • 889
  • 5
  • 14
  • 32

4 Answers4

25

I'd suggest you to you jest.spyOn. It's a really handy way to spy on a function and/or attach some mock behaviour.

You can use it like this:

import { activeListItem } from './utils';

let spy;
beforeAll(() => {
  spy = jest.spyOn(document, 'getElementById');
});

describe('activeListItem', () => {
  describe('with found element', () => {
    let mockElement;
    beforeAll(() => {
      // Here you create the element that the document.createElement will return
      // It might be even without an id
      mockElement = document.createElement(....);
      spy.mockReturnValue(mockElement);
    });

    // And then you could expect it to have the background
    it('should have the background applied', () => {
      expect(mockElement.style.backgroundColor).toBe('#ededed');
    });
  });

  describe('without found element', () => {
    // And here you can create a scenario
    // When document.createElement returns null
    beforeAll(() => {
      spy.mockReturnValue(null);
    });

    // And expect you function not to throw an error
    it('should not throw an error', () => {
      expect(() => activeListItem({id:'123'})).not.toThrow();
    });
  });
});

It's also a good idea to mock the .scss file, since it's a dependency of your utility file, so that when it's change it won't affect your unit test.

dochawk
  • 7
  • 4
Teneff
  • 30,564
  • 13
  • 72
  • 103
8

There are two options I can think of, you can opt either of them:

  1. Put a check on listItem of function activeListItem

    export function activeListItem(props) {
         let listItem = document.getElementById(props.id);
         if (listItem === null) {
              return;
         }
         listItem.style.backgroundColor = variables.whiteGray;
         return listItem;
     }
    
  2. Add dummy element in your test case

    it('check if the correct color is set for the acitve list item', () => {
       /** Create and add dummy div **/
        let testId = "dummy-testId";
        let newDiv = document.createElement("div");
        newDiv.setAttribute("id", testId);
        document.body.appendChild(newDiv);
    
        let props = {id: testId}
        const listItem = activeListItem(props);
        expect(listItem.style.backgroundColor).toBe('#ededed');
    });
    
paragxviii
  • 220
  • 1
  • 9
  • Document in *let newDiv = document.createElement("div");* will be null. As I correct that error will occur? – Patryk Jun 27 '19 at 10:27
  • your second option almost works but expect(listItem.style.backgroundColor) is empty I think there must be a problem with importing the variables.scss. If I change `variables.whiteGray` to `#ededed` it works – WeSt Jun 27 '19 at 10:49
  • Try [this](https://stackoverflow.com/questions/41040269/syntax-error-when-test-component-with-sass-file-imported) to import scss in test files – paragxviii Jun 28 '19 at 04:15
1

This line have problem:

 let listItem = document.getElementById(props.id);

Create element in the first place for mocking in jest. Be sure to wait for document and inject it.

What you doing is getting element when isn't ready to test / non exist in this context.

--- EDITED TO ADD EXAMPLE ---

What need to be added: https://jestjs.io/docs/en/configuration#setupfiles-array

Others response to similar problem with example solution: https://stackoverflow.com/a/41186342/5768332

Patryk
  • 329
  • 3
  • 12
0

A combination of the top two answers worked well for me:

...

beforeAll(() => {
  const modalDiv = document.createElement("div");
  modalDiv.setAttribute("id", "react-modal-container");
  document.body.appendChild(modalDiv);
});

...
Akaisteph7
  • 5,034
  • 2
  • 20
  • 43