Setting up tape testing framework for basic frontend development

vonheikemen profile image Heiker ・6 min read

Today we are going to learn how we can use tape to test code that is meant to run in a browser.

You can checkout the source code examples on github

What is tape?

Tape is a javascript testing framework that provides only essential feature set so you can make assertions about your code.

Why use tape?

This is the part where I try to sell you tape, but I wont do that.

If you navigate in the interwebs in search for more information about it, you'll probably find someone that tells you that the simplicity of this framework will magically make your test (and your whole codebase) more maintainable. Please don't fall for that.

If you find yourself needing to mock ajax calls, or websocket connections, or need to monkey patch your module requires, then I suggest you start to look for a more "feature complete" testing framework like jest. Or checkout cypress.

Use tape if you see that the limited features it provides fit your needs.

Lets use the stuff

Begin with installing tape.

npm install -D tape@4.9.1

Now for a test drive we will create a simple.test.js file inside of a folder named test. Next, we create a test.

// ./test/simple.test.js

var test = require('tape');

test('1 + 1 equals 2', function(t) {
  var sumResult = 1 + 1;
  t.equals(sumResult, 2);

So what's happening in here?

On the first line we require tape, like we would any other module within our "regular" codebase. Then we store the only function that it exposes in a variable. We are using require and not import for now, but we'll fix that later.

Then we call test. The first parameter is a title, a string that should describe what we are testing. The second parameter is the actual test, which we pass as a callback.

You'll notice that we get an object in our callback. This object is our assertion utility. It has a set of methods that display useful messages when the assertions fail. In here I'm calling it t because that's how you find it in the documentation.

Finally we explicitly tell tape that the test needs to end using t.end().

What's interesting about tape is the fact that is not some super complex testing environment. You can execute this test like any other script using node. So you could simply write node ./test/simple.test.js on the terminal and get the output report.

$ node ./test/simple.test.js

TAP version 13
# 1 + 1 equals 2
ok 1 should be equal

# tests 1
# pass  1

# ok

If you want to execute more than one test file you can use the binary that tape provides. This will give you access to a command named tape and pass a glob pattern. For example, to execute every test file that match anything that ends with .test.js inside a folder named test, we could write an npm script with this:

tape './test/**/*.test.js'

Using ES6 modules

There is a couple of ways we can achieve this.

With babel-register

If you have babel already installed and configured with your favorite presets and plugins, you can use @babel/register to compile your testing files with the same babel config that you use for your source code.

npm install -D @babel/register@7.0.0

And then you can use the tape command with the -r flag to require @babel/register. Like this:

tape -r '@babel/register' './test/**/*.test.js'

With require hooks

Warning: This wont work with babel 7.

Another approach to solve this is by using require-extension-hooks in a setup script.

npm install -D require-extension-hooks@0.3.3 require-extension-hooks-babel@0.1.1

Now we create a setup.js with the following content.

// ./test/setup.js

const hooks = require('require-extension-hooks');

// Setup js files to be processed by `require-extension-hooks-babel`

And finally we require it with -r flag in our tape command.

tape -r './test/setup' './test/**/*.test.js'

With esm

We could still use import statements even if we don't transpile our code. With the esm package we can use ES6 modules in a node environment.

npm install -D esm@3.0.82

And use it with tape.

tape -r 'esm' './test/**/*.test.js'

For more information about esm see this article

Testing the DOM

Imaging that we have this code right here:

// ./src/index.js

// this example was taken from this repository:
// https://github.com/kentcdodds/dom-testing-library-with-anything

export function countify(el) {
  el.innerHTML = `
  const button = el.querySelector('button')
  button._count = 0
  button.addEventListener('click', () => {
    button.textContent = button._count

What we got here (besides a disturbing lack of semicolons) is an improvised "component" that has a button that counts the number of times it has been clicked.

And now we will like test this by triggering a click event in this button and checking if the DOM actually updated. This is how I would like to test this code:

import { countify } from '../src/index';

test('counter increments', t => {
  // "component" setup
  var div = document.createElement('div');

  // search for the button with the good old DOM API
  var button = div.getElementsByTagName('button')[0];

  // trigger the click event
  button.dispatchEvent(new MouseEvent('click'));

  // make the assertion
  t.equals(button.textContent, '1');

  // end the test

Sadly if we try to run this test it would fail for a number of reasons, number one being that document doesn't exists in node. But we'll see how we can overcome that.

The fake DOM way

If you would like to keep executing your test in the command line you could use JSDOM in order to use a DOM implementation that works in node. Because I'm lazy I'll be using a wrapper around JSDOM called browser-env to setup this fake environment.

npm install -D browser-env@3.2.5

And now we create a setup script.

// ./test/setup.js

import browserEnv from 'browser-env';

// calling it this way it injects all the global variables
// that you would find in a browser into the global object of node

// Alternatively we could also pass an array of variable names
// to specify which ones we want.
// browserEnv(['document', 'MouseEvent']);

With this in place we are ready to run the test and watch the results.

$ tape -r 'esm' -r './test/setup' './test/**/*.test.js'

TAP version 13
# counter increments
ok 1 should be equal

# tests 1
# pass  1

# ok

But what if you don't trust in JSDOM or simply think is a bad idea to inject global variables in the node process that runs your test, you can try this in different way.

Using the real deal

Because tape is a simple framework it is posible to run the test in a real browser. You may already be using a bundler (or maybe not) to compile your code, we can use that to compile our test and run them in the browser.

For this particular example I will show the minimum viable webpack config to get this working. So lets start.

npm install -D webpack@4.19.0 webpack-cli@3.1.0 webpack-dev-server@3.1.8 html-webpack-plugin@3.2.0

Let the config begins...

// ./webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { join } = require('path');

module.exports = {
  entry: join(__dirname, 'test', 'simple.test.js'),
  mode: 'development',
  devtool: 'inline-source-map',
  plugins: [
    new HtmlWebpackPlugin()
  node: {
    fs: 'empty'

Let me walk you through it.

  • entry is the test file that we want to compile. Right now this entry point is a test file, but you can leverage webpack features to bundle multiple test files.
  • mode is set in development so webpack can do its magic and make fast incremental builds.
  • devtool is set to inline-source-map so we can debug the code in the browser.
  • plugins we only have one, the html plugin creates an index.html file that is used by the development server.
  • node is set with fs: 'empty' because tape uses this module in their source, but since it doesn't exists in the browser we tell webpack to set it as an empty object.

Now if you use the webpack-dev-server command, and open a browser on localhost:8080 you'll see nothing but if you open the browser console you'll see tape's test output.

Other sources

Thanks for reading.


Editor guide