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
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:
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'
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');
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'
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
})
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' }
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.
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.
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)
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!!!!!!
You're welcome! Let me know if you discover anything interesting in your testing!
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.
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.Hi there! It's a great article and all, but how do I test individual functions, variables, etc. in an external JavaScript file?
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...
Hi there! There are issues with ESM and what if these JS functions have DOM manipulations? Those seem to be my issues.
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?