DOM Manipulation

Another class of functions that is often considered difficult to test is code that directly manipulates the DOM. Let’s see how we can test the following snippet of jQuery code that listens to a click event, fetches some data asynchronously and sets the content of a span.

displayUser.js

  1. 'use strict';
  2. const $ = require('jquery');
  3. const fetchCurrentUser = require('./fetchCurrentUser.js');
  4. $('#button').click(() => {
  5. fetchCurrentUser(user => {
  6. const loggedText = 'Logged ' + (user.loggedIn ? 'In' : 'Out');
  7. $('#username').text(user.fullName + ' - ' + loggedText);
  8. });
  9. });

Again, we create a test file in the __tests__/ folder:

__tests__/displayUser-test.js

  1. 'use strict';
  2. jest.mock('../fetchCurrentUser');
  3. test('displays a user after a click', () => {
  4. // Set up our document body
  5. document.body.innerHTML =
  6. '<div>' +
  7. ' <span id="username" />' +
  8. ' <button id="button" />' +
  9. '</div>';
  10. // This module has a side-effect
  11. require('../displayUser');
  12. const $ = require('jquery');
  13. const fetchCurrentUser = require('../fetchCurrentUser');
  14. // Tell the fetchCurrentUser mock function to automatically invoke
  15. // its callback with some data
  16. fetchCurrentUser.mockImplementation(cb => {
  17. cb({
  18. fullName: 'Johnny Cash',
  19. loggedIn: true,
  20. });
  21. });
  22. // Use jquery to emulate a click on our button
  23. $('#button').click();
  24. // Assert that the fetchCurrentUser function was called, and that the
  25. // #username span's inner text was updated as we'd expect it to.
  26. expect(fetchCurrentUser).toHaveBeenCalled();
  27. expect($('#username').text()).toBe('Johnny Cash - Logged In');
  28. });

We are mocking fetchCurrentUser.js so that our test doesn’t make a real network request but instead resolves to mock data locally. This ensures that our test can complete in milliseconds rather than seconds and guarantees a fast unit test iteration speed.

Also, the function being tested adds an event listener on the #button DOM element, so we need to set up our DOM correctly for the test. jsdom and the jest-environment-jsdom package simulate a DOM environment as if you were in the browser. This means that every DOM API that we call can be observed in the same way it would be observed in a browser!

To get started with the JSDOM test environment, the jest-environment-jsdom package must be installed if it’s not already:

  • npm
  • Yarn
  • pnpm
  1. npm install --save-dev jest-environment-jsdom
  1. yarn add --dev jest-environment-jsdom
  1. pnpm add --save-dev jest-environment-jsdom

The code for this example is available at examples/jquery.