Unit testing web components is tricky. The customElements
API is required (or a polyfill of it) to create web component instances for testing.
Due to this friction and complication, I typically use an E2E testing framework like Cypress which runs tests against a real browser like headless Chrome.
Whilst an E2E framework is a fantastic tool for testing web components, I've wanted a suite of unit tests like I have for a back-end API - a comprehensive suite of fast tests for validating web components.
With Node v14, this can now be achieved through the native support for ES modules.
This post provides an example of how to set-up web component unit testing using ava and jsdom.
A repo containing the source code for this example is available at https://github.com/ducksoupdev/ficusjs-ava-test.
Dependencies
The following dependencies are used for this example.
Ava
Ava is a lightweight, fast test runner for NodeJS.
Jsdom
Jsdom is a pure Javascript implementation of HTML and DOM. Starting with v16, jsdom provides a customElements
API implementation.
Set-up
Firstly, initialize a new NodeJS project to create a package.json
file using npm init
.
Copy the following properties to the package.json
file.
{
"scripts": {
"test": "ava"
},
"devDependencies": {
"@ficusjs/renderers": "^3.1.0",
"ava": "^3.15.0",
"ficusjs": "^3.2.3",
"jsdom": "^16.5.2"
}
}
Secondly, create an ES module (.mjs
) test file at test/component.spec.mjs
.
Copy the following to the file.
import { JSDOM } from 'jsdom'
import test from 'ava'
test.before(t => {
const dom = new JSDOM('<!DOCTYPE html><html><head></head><body></body></html>')
globalThis.window = dom.window
globalThis.document = dom.window.document
globalThis.customElements = window.customElements
globalThis.HTMLElement = window.HTMLElement
})
This contains the basic structure of a unit test:
- Imports
jsdom
andava
- Initializes a new DOM and exposes global properties required during testing
Create a web component
Create an ES module file at src/component.mjs
and copy the following contents into it.
import { createComponent } from 'ficusjs'
import { html, renderer } from '@ficusjs/renderers'
createComponent('basic-comp', {
renderer,
render () {
return html`<p>Basic component</p>`
}
})
The example is a web component created using FicusJS and the uhtml renderer. It is a basic example that renders a <p>
tag internally.
Writing a test
Lets test the component.
Open the ES module test file test/component.spec.mjs
.
Copy the following to the file after the existing content.
test('render basic component', async t => {
await import('../src/component.mjs')
const document = globalThis.document
const basicComp = document.createElement('basic-comp')
document.body.appendChild(basicComp)
t.is(document.querySelector('basic-comp p').textContent, 'Basic component')
})
The following is happening in this test:
- Import the component ready to test
- Create a new instance of the component and append it to the
<body>
- Assert that the component renders the
<p>
tag internally
The component is imported within the test and not at the top of the file because the global properties must be initialized for the component to work. Importing the component during the test means that the required global properties are available for the component to register itself with the customElements
API.
Run the test
To run the test, type npm test
.
If all is successful, you should see the above output from ava
.
Summary
The opportunity to unit test web components this way is exciting. However, there are some caveats with this approach that might make it difficult in certain scenarios.
- It follows NodeJS ES module guidelines that may not be compatible with the browser.
- Files must be named with the
.mjs
extension or provide apackage.json
top level propertytype
of valuemodule
if the extension is.js
. - Importing libraries from a URL is not natively supported but can be achieved through a custom HTTPS loader.
ES module support in NodeJS is relatively new and I'm sure will get better in time.
For applications that use NodeJS build tools such as Rollup or Webpack, this could provide a unit testing approach to validate web components bundled together.
Top comments (2)
This approach utilises native ES modules which means you can share the browser code with NodeJS without transpilation. I believe it makes it better as it reduces the complexity. Currently Jest does not support native ES modules although this will probably change in the future.
How does this compare with something like Jest? Do you find it better to use?