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 thewindow
object, 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.
Figure1.The JavaScript environment within a browser.
Differently from the browser, Node can’t run that script. Try executing it withnode main.js
and Node will immediately tell you that it has found aReferenceError
because “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 theincrementCount
function, 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 sameReferenceError
you’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.html
and which we can use in Node.
First, create apackage.json
file withnpm init -y
and then install JSDOM withnpm install jsdom
.
By usingfs
you will read theindex.html
file 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;
Thepage
representation contains properties that you’d find in a browser, like, for example,window
. Because you’re now dealing with pure JavaScript, you can usepage
in Node.
Try importingpage
in 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’stestEnvironment
option, you can make it set up a global instance of JSDOM which you can use when running your tests.
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 whosetestEnvironment
property’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,jsdom
is the default value for Jest’stestEnvironment
, so you don’t necessarily need to specify it.
If you don’t want to create ajest.config.js
file manually, you can use./node_modules/.bin/jest --init
to automate this process. Jest’s automatic initialization will then prompt you to choose a test environment and present you ajsdom
option.
Now try to create amain.test.js
file and importmain.js
to 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 thoughwindow
now 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 itsaddEventListener
method.
To useindex.html
as the page that the JSDOM instance will use, you need to readindex.html
and assign its content towindow.document.body.innerHTML
before 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 globalwindow
to use the contents ofindex.html
, Jest will be able to executemain.test.js
successfully.
The last step you need to take to be able to write a test forincrementCount
is to expose it. Becausemain.js
does not exposeincrementCount
nordata
, you can’t exercise the function, nor check its result. Solve this problem by usingmodule.exports
to exportdata
and theincrementCount
function.
Listing9.main.js
// ...module.exports = { incrementCount, data };
Finally, you can go ahead and create amain.test.js
file which sets an initial count, exercisesincrementCount
and checks the newcount
withindata
. 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 exposeincrementCount
anddata,main.js
will now throw an error when running in the browser. To see the error, try serving your application again withnpx http-server ./
, and accessinglocalhost:8080
with your browser’s devtools open.
Uncaught ReferenceError: module is not defined at main.js:14
Your browser throws this error because it doesn’t havemodule
globally 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 likewebpack
andbrowserify
is to do this kind of bundling.
Installbrowserify
as adev-dependency
and run./node_modules/.bin/browserify main.js -o bundle.js
to transform yourmain.js
file into a browser-friendlybundle.js
.
NOTE: You can find Browserify’s complete documentation at browserify.org.
Once you have runbrowserify
, updateindex.html
to usebundle.js
instead 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.js
whenever there’s a change tomain.js
.
Because you have to run it frequently, it would be wise to create an NPM script which runsbrowserify
with the correct arguments.
To create an NPM script which runsbrowserify
, update yourpackage.json
so 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 likebrowserify
orwebpack
, 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 multiplescript
tags 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 howbrowserify
can 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.
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.
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.
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? ›- 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).
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? ›- Do the homework assignments daily. ...
- Write down all hard to remember formulas, equations, and rules as soon as you get the test. ...
- Read directions carefully. ...
- Show all work. ...
- Skip hard problems. ...
- Recheck problems. ...
- Write legibly.
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.