34

When I try to unit test the getElement function

class BarFoo {
    getElement() {
        return document.querySelector('#barfoo');
    }
}

mocha doesn't know anything about document, so I figured that you might do something like this:

beforeEach(() => {
    global.document = {
        querySelector: () => { ... }
    }
}

Although this works, I'm wondering if this is the correct approach and maybe there is a package available to solve this issue, because my approach can get laborious if more browser API's are used ?

Jeanluca Scaljeri
  • 26,343
  • 56
  • 205
  • 333

2 Answers2

27

There are a few options available to you:

Option 1: Use JSDOM

By adding a DOM to your code, you can unit test much of your client-side code within node.js

Option 2: Use MOCHA on the client

Mocha does run inside the client and you can use separate client-side unit tests. This tends to be my preferred approach as I can test against specific browsers and not a specific JS implantation.

Option 3: Use PhantomJS

PhantomJS allows you to control a headless browser within your testing environment.

Option 4: Headless Chrome

Now that Headless Chrome is out, the PhantomJS maintainer has retired.

Jeremy J Starcher
  • 23,369
  • 6
  • 54
  • 74
18

I have been writing tests similar to what you proposed when I just needed to mock a certain function on window:

it('html test', function () {
    const windowRef = global.window;
    global.window = {document: {querySelector: () => null}};
    const lib = require('lib-that-uses-queryselector');
    assert(true);
    global.window = windowRef;
});

I have been using mock-browser in other tests when I wanted a more complete window object:

it('html test', function () {
     const windowRef = global.window;
     const MockBrowser = require('mock-browser').mocks.MockBrowser;
     global.window = new MockBrowser().getWindow();
     const lib = require('lib-that-uses-window');
     assert(true);
     global.window = windowRef;
});

Note that you probably want to restore the window object (global.window = windowRef; above) after messing with globals.

Simon Bengtsson
  • 7,573
  • 3
  • 58
  • 87
  • 1
    global.window worked for me, highly recommend it as you don't need any extra libs. – azz0r May 26 '17 at 12:57
  • I used the idea here but I prefer const window = global.window; global.window = {...}; ... global.window = window; In case a dumby window object ever gets added at a global scale. – Blue Nov 08 '17 at 21:35
  • True! Edited to reflect that now. – Simon Bengtsson Nov 09 '17 at 09:23
  • 1
    @SimonBengtsson, `global.window = window` will not restore the widow object since the window const created with `window = global.window` is a copy by reference. Instead you can clone the window at the top of the test with: `preservedWindow = Object.assign({}, window)` then reassign when done testing: `global.window = preservedWindow` (note that could still be problematic since Object.assign only does a shallow copy, but should be good enough in most cases) – iammatthew2 Dec 13 '18 at 17:25
  • Hmm, wouldn't it be better to use the original window object instead of a copy? – Simon Bengtsson Dec 13 '18 at 17:58