Testing with Node, Jest, and JSDOM - Manning (2024)

Testing with Node, Jest, and JSDOM - Manning (1)

From Testing JavaScript Applications by Lucas da Costa

In this article, you’ll learn how to use Node and Jest to test code written to run in a browser.

Take 40% off Testing JavaScript Applications by entering fccdacosta into the discount code box at checkout at manning.com.

Baking in a professional kitchen is quite different from baking at home. At home, you won’t always have all the unique ingredients you would find on a chef’s shelves. You probably won’t have the same fancy appliances, or the same impeccable kitchen. Nevertheless, that doesn’t mean you can’t bake excellent desserts. You’ve just got to adapt.

Similarly, running JavaScript in a browser is significantly different from running JavaScript in Node. Depending on the occasion, the JavaScript code running in a browser can’t run in Node at all, and vice-versa. Therefore, for you to test your front-end application, you’ll have to jump through a few extra hoops, but it doesn’t mean you can’t do it. With a few adaptations, you can use Node to run JavaScript that has been written for the browser in the same way that Louis can bake mouth-watering cheesecakes at home without the fancy French cookware he’s got at the bakery.

Within a browser, JavaScript has access to different APIs and thus has different capabilities.

In browsers, JavaScript has access to a global variable calledwindow. Through thewindowobject, you can change a page’s content, trigger actions in a user’s browser, and react to events like clicks and keypresses.

Throughwindow, you can, for example, attach a listener to a button so that each time a user clicks it, your application updates the quantity of an item in the bakery’s inventory.

Try creating an application which does exactly that. Write an HTML file that contains a button, a count, and which loads a script calledmain.js.

Listing1.index.html

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Inventory Manager</title></head><body> <h1>Cheesecakes: <span id="count">0</span></h1> <button id="increment-button">Add cheesecake</button> #1 <script src="main.js"></script></body></html>

#1 The script with which we’ll make the page interactive.

Inmain.js, find the button by its ID and attach a listener to it. Whenever users click this button, the listener will be triggered, and the application will increment the cheesecake count.

Listing2.main.js

let data = { count: 0 };const incrementCount = () => { data.cheesecakes++; window.document.getElementById("count") .innerHTML = data.cheesecakes;};const incrementButton = window.document.getElementById("increment-button");incrementButton.addEventListener("click", incrementCount);

#1 The function which updates the application’s state

#2 Attaching an event listener which will cause incrementCount to be called whenever the button is clicked

To see that page in action, executenpx http-server ./in the same folder as yourindex.html, and then accesslocalhost:8080.

Because this script runs in a browser, it has access towindow, and thus it can manipulate the browser and the elements in the page.

Testing with Node, Jest, and JSDOM - Manning (2)

Figure1.The JavaScript environment within a browser.

Differently from the browser, Node can’t run that script. Try executing it withnode main.jsand Node will immediately tell you that it has found aReferenceErrorbecause “window is not defined.”

That error happens because Node doesn’t have awindow. Instead, because it was designed to run different kinds of applications, it gives you access to APIs like process, which contains information about the current Node.js process, andrequire, which allows you to import different JavaScript files.

For now, if you were to write tests for theincrementCountfunction, you’d have to run them in the browser. Because your script depends on DOM APIs, you wouldn’t be able to run these tests in Node. If you tried to do it, you’d run into the sameReferenceErroryou’ve seen when you executednode main.js. Given that Jest depends on Node-specific APIs and thereforeonlyruns in Node, you can’tuse Jest also.

To be able to run your tests in Jest,instead of running your tests within the browser, you can bring browser APIs to Node by using JSDOM. You can think of JSDOM as an implementation of the browser environment which can run within Node. It implements web standards using pure JavaScript. With JSDOM you can emulate, manipulating the DOM and attaching event listeners to elements, for example.

JSDOM JSDOM is an implementation of web standards written in purely in JavaScript which you can use in Node.

To understand how JSDOM works, let’s use it to create an object that representsindex.htmland which we can use in Node.

First, create apackage.jsonfile withnpm init -yand then install JSDOM withnpm install jsdom.

By usingfsyou will read theindex.htmlfile and pass its contents to JSDOM, so that it can create a representation of that page.

Listing3.page.js

const fs = require("fs");const { JSDOM } = require("jsdom");const html = fs.readFileSync("./index.html");const page = new JSDOM(html);module.exports = page;

Thepagerepresentation contains properties that you’d find in a browser, like, for example,window. Because you’re now dealing with pure JavaScript, you can usepagein Node.

Try importingpagein a script and interacting with it as you’d do in a browser. You can try, for example, attaching a new paragraph to thepage.

Listing4.example.js

const page = require("./page"); #1console.log("Initial page body:");console.log(page.window.document.body.innerHTML);const paragraph = page.window.document.createElement("p"); #2paragraph.innerHTML = "Look, I'm a new paragraph"; #3page.window.document.body.appendChild(paragraph); #4console.log("Final page body:");console.log(page.window.document.body.innerHTML);

#1 Importing the JSDOM representation of the page.

#2 Creating a paragraph element

#3 Updating the paragraph’s content

#4 Attaching the paragraph to the page

To execute the above script in Node, runnode example.js.

With JSDOM, you can do almost everything you can do in a browser, including updating DOM elements, likecount.

Listing5.example.js

const page = require("./page");// ...console.log("Initial contents of the count element:");console.log(page.window.document.getElementById("count").innerHTML);page.window.document.getElementById("count").innerHTML = 1337;console.log("Updated contents of the count element:"); #1console.log(page.window.document.getElementById("count").innerHTML);// ...

#1 Updating the contents of the count element

Thanks to JSDOM, you can run your tests in Jest, which, as I have mentioned, can only run in Node.

By using passing the value"jsdom"to Jest’stestEnvironmentoption, you can make it set up a global instance of JSDOM which you can use when running your tests.

Testing with Node, Jest, and JSDOM - Manning (3)

Figure2.The JavaScript environment within Node.

To set up a JSDOM environment within Jest, start by creating a new Jest configuration file calledjest.config.js. In this file, export an object whosetestEnvironmentproperty’s value is"jsdom".

Listing6.jest.config.js

module.exports = { testEnvironment: "jsdom",};

NOTE: At the time of writing, Jest’s current version is 24.9. In this version,jsdomis the default value for Jest’stestEnvironment, so you don’t necessarily need to specify it.

If you don’t want to create ajest.config.jsfile manually, you can use./node_modules/.bin/jest --initto automate this process. Jest’s automatic initialization will then prompt you to choose a test environment and present you ajsdomoption.

Now try to create amain.test.jsfile and importmain.jsto see what happens.

Listing7.main.test.js

require("./main");

If you try to run this test with Jest, you will still get an error.

FAIL ./main.test.js● Test suite failed to run TypeError: Cannot read property 'addEventListener' of null 10 | 11 | const incrementButton = window.document.getElementById("increment-button"); > 12 | incrementButton.addEventListener("click", incrementCount);

Even thoughwindownow exists thanks to Jest setting upJSDOM, its DOM is not built fromindex.html. Instead, it’s built from an empty HTML document, and thus there is noincrement-button. Because the button does not exist, you can’t call itsaddEventListenermethod.

To useindex.htmlas the page that the JSDOM instance will use, you need to readindex.htmland assign its content towindow.document.body.innerHTMLbefore importingmain.js.

Listing8.main.test.js

const fs = require("fs");window.document.body.innerHTML = fs.readFileSync("./index.html");require("./main");

#1 Assigning the contents of the index.html file to the page’s body

Because you have now configured the globalwindowto use the contents ofindex.html, Jest will be able to executemain.test.jssuccessfully.

The last step you need to take to be able to write a test forincrementCountis to expose it. Becausemain.jsdoes not exposeincrementCountnordata, you can’t exercise the function, nor check its result. Solve this problem by usingmodule.exportsto exportdataand theincrementCountfunction.

Listing9.main.js

// ...module.exports = { incrementCount, data };

Finally, you can go ahead and create amain.test.jsfile which sets an initial count, exercisesincrementCountand checks the newcountwithindata. We’re using the 3A pattern here — arrange, act, assert.

Listing10.main.test.js

const fs = require("fs");window.document.body.innerHTML = fs.readFileSync("./index.html");const { incrementCount, data } = require("./main");describe("incrementCount", () => { test("incrementing the count", () => { data.cheesecakes = 0; #1 incrementCount(); #2 expect(data.cheesecakes).toBe(1); #3 });});

#1 Arrange: set the initial quantity of cheesecakes.

#2 Act: exercise the incrementCount function, which is the unit under test.

#3 Assert: check whether data.cheesecakes contains the correct amount of cheesecakes.

Once you’ve celebrated seeing this test pass, it’s time to solve one last problem.

Because you’ve usedmodule.exportsto exposeincrementCountanddata,main.jswill now throw an error when running in the browser. To see the error, try serving your application again withnpx http-server ./, and accessinglocalhost:8080with your browser’s devtools open.

Uncaught ReferenceError: module is not defined at main.js:14

Your browser throws this error because it doesn’t havemoduleglobally available. Again, you have run into a problem related to the differences between browsers and Node.

A common strategy to run in browsers files which use Node’s module system is to use a tool that bundles dependencies into a single file that the browser can execute. One of the main goals of tools likewebpackandbrowserifyis to do this kind of bundling.

Installbrowserifyas adev-dependencyand run./node_modules/.bin/browserify main.js -o bundle.jsto transform yourmain.jsfile into a browser-friendlybundle.js.

NOTE: You can find Browserify’s complete documentation at browserify.org.

Once you have runbrowserify, updateindex.htmlto usebundle.jsinstead ofmain.js.

Listing11.index.html

<!DOCTYPE html><html lang="en"> <!-- ... --> <body> <!-- ... --> <script src="bundle.js"></script> </body></html>

#1 The bundle.js will be generated from main.js. It’s a single file which contains all of main.js direct and indirect dependencies.

TIP: You will need to rebuildbundle.jswhenever there’s a change tomain.js.

Because you have to run it frequently, it would be wise to create an NPM script which runsbrowserifywith the correct arguments.

To create an NPM script which runsbrowserify, update yourpackage.jsonso that it includes the lines below.

Listing12.package.json

{ // ... "scripts": { // ... "build": "browserify main.js -o bundle.js" }, // ...}

#1 Goes through the main.js file’s dependency tree and bundles all of the dependencies into a single bundle.js file.

By using tools likebrowserifyorwebpack, you can transform the testable code you’ve written to run in Node so that it can run in a browser.

Using bundlers enables you to test your modules separately, and makes it easier to manage them within browsers. When you bundle your application into a single file, you don’t need to manage multiplescripttags in your HTML page.

In this article, you’ve learned how to use Node and Jest to test JavaScript designed to run in a browser. You’ve seen the differences between these two platforms and learned how to bring browser APIs to Node with JSDOM.

You’ve also seen howbrowserifycan help you test your application by enabling you to divide it into separate modules, which you can test in Node and then bundle to run in a browser.

By using these tools, you are able to test your browser application in Node, using Jest.

That’s all for now.

If you want to learn more about the book, you can check it out on our browser-based liveBook platform here.

Comments are closed.

Testing with Node, Jest, and JSDOM - Manning (2024)

FAQs

What is the difference between Jsdom and Node? ›

JSDOM environment is slower than Node

JSDOM is a JavaScript implementation of the WHATWG DOM and HTML standards. In other words, jsdom simulates a browser's environment without running anything but plain JS. It runs tests fast but not as fast as pure Node. The difference can be two-fold.

Why do my Jest tests take so long? ›

Populating the cache. The first time we run tests in our application, Jest will need to take a bit longer as it can't take advantage of cached data. Jest spends the majority of the first time it runs transpiling TypeScript.

How to write unit tests in nodejs with jest test library? ›

Before you can start writing unit tests with Jest, you have to install it as a dependency in your project and create test files. The folder created by the command above will store all your test files. Next, you will create a test file. Test files are files you write your tests in.

How can I make my Jest test faster? ›

Over the years, Jest has undergone various performance improvements that can significantly boost the speed of test execution. One way to take advantage of these improvements is to have the latest version of Node installed in your project. Also it sould be configured with the latest version of Jest.

What is the difference between DOM element and DOM node? ›

Elements vs Nodes

Generally when you are working with the DOM you will be working with elements since most often you want to interact with HTML elements. Nodes are the more generic version of an element. A node could be an HTML element, but it could also be anything else in an HTML document, such as text or comments.

Which node js version is best? ›

Most users are advised to use the LTS version. New versions of NodeJS involve enhanced performance, the latest features, and bug fixes. The versions of NodeJS are represented with x, y, and z, where x is used to depict the significant or major changes and others are used to depict the minor changes.

What is the disadvantage of Jest testing? ›

The primary drawbacks of Jest are due to its youth and lack of popularity among JavaScript developers. This kind of technology can be really helpful at times, such as when you can run and debug your tests in an IDE like WebStorm. WebStorm didn't even support running Jest tests till recently.

What are the cons of Jest testing? ›

Limitations of Using Jest
  • Using auto-mocking features might cause your test suite to run slowly. ...
  • Jest snapshot testing is unsuitable for projects that create large snapshot files with thousands of lines.
  • It has fewer tools and support than more established libraries (like Mocha).
Dec 29, 2022

Is Jest good for end to end testing? ›

js development, you can use a combination of the Chrome API Puppeteer and the JavaScript testing framework Jest to automate e2e testing, allowing you to ensure that the user interface (UI) of your application is still functioning as you fix bugs and add new features.

Is Jest for frontend or backend? ›

Jest is a JavaScript-based testing framework that lets you test both front-end and back-end applications. Jest is great for validation because it comes bundled with tools that make writing tests more manageable.

Can I use Jest to test NodeJS? ›

For your NodeJS applications, Jest framework can be used for Unit Testing.

How to write test cases for API in node js using Jest? ›

Testing a simple API using jest and supertest. Let's start off building a very simple application – we'll add a database and more routes later, let's just focus on the syntax for now. const express = require("express"); const app = express(); const bodyParser = require("body-parser"); app. use(bodyParser.

What is the fastest Jest test runner? ›

Wallaby is the fastest available JavaScript test runner. Jest in watch mode (in the best case scenario) re-runs all tests in all test files related to changed files based on hg/git uncommitted files.

How fast should unit tests run? ›

It really isn't something that a lot of people think about but over time can be come a problem that will have you regretting some of your early decisions. Typically the response I get when I ask this question is each test should take anywhere from 0.01 seconds to 1 second max to run in isolation.

How can I get better at tests? ›

The following are just suggestions to improve your study skills.
  1. Do the homework assignments daily. ...
  2. Write down all hard to remember formulas, equations, and rules as soon as you get the test. ...
  3. Read directions carefully. ...
  4. Show all work. ...
  5. Skip hard problems. ...
  6. Recheck problems. ...
  7. Write legibly.

What is Jsdom in Nodejs? ›

JSDOM is a library which parses and interacts with assembled HTML just like a browser. The benefit is that it isn't actually a browser. Instead, it implements web standards like browsers do. You can feed it some HTML, and it will parse that HTML.

What is the difference between node and Nestjs? ›

js are excellent for creating web apps. While Nest. js offers a more ordered and systematic approach to developing web apps, Node. js can create high-performance and scalable real-time applications.

When to use jsdom? ›

To be able to run your tests in Jest, instead of running your tests within the browser, you can bring browser APIs to Node by using JSDOM. You can think of JSDOM as an implementation of the browser environment which can run within Node. It implements web standards using pure JavaScript.

What does node mean in js? ›

js (Node) is an open source, cross-platform runtime environment for executing JavaScript code. Node is used extensively for server-side programming, making it possible for developers to use JavaScript for client-side and server-side code without needing to learn an additional language.

References

Top Articles
Latest Posts
Article information

Author: Amb. Frankie Simonis

Last Updated:

Views: 6377

Rating: 4.6 / 5 (56 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Amb. Frankie Simonis

Birthday: 1998-02-19

Address: 64841 Delmar Isle, North Wiley, OR 74073

Phone: +17844167847676

Job: Forward IT Agent

Hobby: LARPing, Kitesurfing, Sewing, Digital arts, Sand art, Gardening, Dance

Introduction: My name is Amb. Frankie Simonis, I am a hilarious, enchanting, energetic, cooperative, innocent, cute, joyous person who loves writing and wants to share my knowledge and understanding with you.