DEV Community

Cover image for How to Unit Test HTML and Vanilla JavaScript Without a UI Framework
Tyler Hawkins
Tyler Hawkins

Posted on • Originally published at levelup.gitconnected.com

How to Unit Test HTML and Vanilla JavaScript Without a UI Framework

Recently I was curious about something: Is it possible to write unit tests for front end code that doesn’t use any sort of UI framework or developer tooling?

In other words, no React, Angular, or Vue. No webpack or rollup. No build tools of any kind. Just a plain old index.html file and some vanilla JavaScript.

Could a setup like this be tested?

This article and its accompanying GitHub repo are the result of that question.


Prior Experience

In my professional life, I’ve done a good bit of testing. I’m primarily a front end software engineer, so my areas of expertise include writing unit tests with Jest as my test framework and either Enzyme or React Testing Library as my test library when working with React. I’ve also done end-to-end testing using Cypress or Selenium.

Generally I choose to build user interfaces with React. When testing these interfaces, I started out using Enzyme years ago, but I’ve since come to favor React Testing Library and the philosophy that you should test your app in the same way that users use your app rather than test implementation details.

Kent C. Dodds’ React Testing Library is built on top of his DOM Testing Library, which, as its name implies, is a library that helps you test the DOM. I thought this might be a good starting point.


Initial Research

Research

Photo by Michael Longmire on Unsplash

It’s very rare in the world of software engineering that you are the first person to attempt something. Nearly everything has been done before in one form or another. For this reason, Google, Stack Overflow, and developer forums are your friend.

I thought that surely someone else has tried this before and has written about it. After doing some research though, it seemed that a few people had tried this in the past but had hit a dead end. One developer asked back in August of 2019 for help but received no replies. Another developer wrote a helpful article on what they came up with, but unfortunately they ended up testing implementation details, which is something I wanted to avoid.

So, with the information I was able to gain from their attempts, I got started making my own demo project.


Demo App

As noted above, you can find the code for my demo app here. You can also view the app in action hosted here. It’s small and simple since this is, after all, just a proof of concept.

Demo apps don’t need to be boring though, so I’ve created a pun generator for your entertainment. Here’s what it looks like:

Demo app

Demo app

When viewing the source code, there are two important files to be aware of:

  • src/index.html: This is the entire app. No other files, just one HTML file with a script tag in it.
  • src/index.test.js: This is the test file. I’m using Jest and DOM Testing Library.

Both files are small, so I’ve included them below:


Source File: index.html


Test File: index.test.js


Overview of the Source File

As you can see in the index.html file, there’s nothing special about it. If you were learning how to create a simple web page for the first time, your result would most likely look pretty similar to this with some basic HTML, CSS, and JavaScript. For simplicity, I’ve included the CSS and JavaScript inline in the file rather than linking to additional source files.

The JavaScript creates an array of puns, adds a click event listener to the button, and then inserts a new pun on the screen each time the button is clicked. Easy enough, right?


Diving into the Test File

Since this is an article about testing, the test file is the key here. Let’s look at some of the more interesting snippets together.


Retrieving the HTML File

The first question I had was how to import the HTML file into the test file. If you were testing a JavaScript file, generally you’d import the exported methods from the file you wanted to test like this:



import { methodA, methodB } from './my-source-file'


Enter fullscreen mode Exit fullscreen mode

However, that approach doesn’t work with an HTML file in my case. Instead, I used the built-in fs Node module to read the HTML file and store it in a variable:



const html = fs.readFileSync(path.resolve(__dirname, './index.html'), 'utf8');


Enter fullscreen mode Exit fullscreen mode

Creating the DOM

Now that I had a string containing the HTML contents of the file, I needed to render it somehow. By default, Jest uses jsdom to emulate a browser when running tests. If you need to configure jsdom, you can also explicitly import it in your test file, which is what I did:



import { JSDOM } from 'jsdom'


Enter fullscreen mode Exit fullscreen mode

Then, in my beforeEach method, I used jsdom to render my HTML so that I could test against it:



let dom
let container

beforeEach(() => {
  dom = new JSDOM(html, { runScripts: 'dangerously' })
  container = dom.window.document.body
})


Enter fullscreen mode Exit fullscreen mode

Running Scripts Inside the jsdom Environment

The most crucial piece to getting this working properly is contained in the configuration options passed to jsdom:



{ runScripts: 'dangerously' }


Enter fullscreen mode Exit fullscreen mode

Because I’ve told jsdom to run the scripts dangerously, it will actually interpret and execute the code contained in my index.html file’s script tag. Without this option enabled, the JavaScript is never executed, so testing the button click events wouldn’t work.

I also like to live dangerously

Disclaimer: It’s important to note that you should never run untrusted scripts here. Since I control the HTML file and the JavaScript inside it, I can consider this safe, but if this script were to be from a third-party or if it included user input, it would not be wise to take this approach to configuring jsdom.


Moment of Truth

Now, after completing the setup described above, when I ran yarn test, it… worked! The proof of concept was a great success, and there was much rejoicing.

Passing tests

All tests pass

Conclusion

So, back to the initial question: Is it possible to write unit tests for front end code that doesn’t use any sort of UI framework or developer tooling?

The answer: Yes!

While my demo app certainly does not reflect what a production-ready app would look like, testing user interfaces this way if needed does seem like a viable option.

Thanks for reading!

Top comments (8)

Collapse
 
jwp profile image
John Peters • Edited

Over the top good news. I find Karma to be untenable most of the time, I want to avoid it, and have written a number of articles to that extent. None of them giving me a total solution. I will try your recommendations as I think, this method would also work for Angular which is my arena.... Thanks Tyler!!!!!!

Collapse
 
thawkin3 profile image
Tyler Hawkins

You're welcome! Let me know if you discover anything interesting in your testing!

Collapse
 
tylerlwsmith profile image
Tyler Smith

Legendary. I've been coding a lot of Vanilla JS lately, and imperative DOM manipulation feels very fragile compared to React. I'm going to use the techniques here to start adding some tests to make sure I don't break anything.

Collapse
 
tromgy profile image
Tromgy

Excellent! Helped me a lot. I was facing exactly the same task. I was aware of the runScripts: 'dangerously' option, but I tried to configure it from jest.config.js without any luck.

Collapse
 
destroyer22719 profile image
Nathan Cai

Hi there! It's a great article and all, but how do I test individual functions, variables, etc. in an external JavaScript file?

Collapse
 
saifahn profile image
sean

That should be simple enough to do just using Jest (or any other testing framework). Export the functions etc. from the Javascript file, import them into a test file and use your testing framework to assess that the functions work correctly. :)

Unless I have misunderstood the question...

Collapse
 
destroyer22719 profile image
Nathan Cai

Hi there! There are issues with ESM and what if these JS functions have DOM manipulations? Those seem to be my issues.

Collapse
 
amin05th profile image
Amin05th

I have a question i tried to move the script to a new file and an the test doesn' t work anymore. What can I do?