Setting up tape testing framework for basic frontend development
Tags: javascript, testing, beginners
Published: 2018-09-18 21:25:38

---
title: Setting up tape testing framework for basic frontend development
published: true
description: steps to use tape testing framework for your basic frontend needs
tags: #javascript #testing #beginners
---

Today we are going to learn how we can use [tape](https://github.com/substack/tape) to test code that is meant to run in a browser. 

> You can checkout the source code examples on [github](https://github.com/VonHeikemen/tape-for-frontend)

# 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](https://jestjs.io/). Or checkout [cypress](https://www.cypress.io/).

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

# Lets use the stuff

Begin with installing tape.

```sh
npm install -D tape@5.2.2
```

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

```javascript
// ./test/simple.test.js

var test = require('tape');

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

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](https://github.com/substack/tape#tplann) 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.

```sh
$ node ./test/simple.test.js

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

1..1
# 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](https://github.com/isaacs/node-glob#glob-primer). 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:

```sh
tape './test/**/*.test.js'
```

## Using ES6 modules

There is a couple of ways we can achieve this.

### With babel-register

> _Warning_: This wont work with node versions that have native support for ES modules. I think this includes Node 12.17 and beyond.

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.

```sh
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:


```sh
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](https://github.com/jackmellis/require-extension-hooks) in a setup script.

```sh
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.

```javascript
// ./test/setup.js

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

// Setup js files to be processed by `require-extension-hooks-babel`
hooks(['js']).plugin('babel').push();
```

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

```sh
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](https://www.npmjs.com/package/esm) package we can use ES6 modules in a node environment.

```sh
npm install -D esm@3.2.25
```

And use it with tape.

```sh
tape -r 'esm' './test/**/*.test.js'
```
> For more information about `esm` see [this article](https://dev.to/bennypowers/you-should-be-using-esm-kn3)

# Testing the DOM

Imagine that we have this code right here:

```javascript
// ./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 = `
    <div>
      <button>0</button>
    </div>
  `
  const button = el.querySelector('button')
  button._count = 0
  button.addEventListener('click', () => {
    button._count++
    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:

```javascript
import test from 'tape';
import { countify } from '../src/index';

test('counter increments', t => {
  // "component" setup
  var div = document.createElement('div');
  countify(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
  t.end(); 
});
```

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](https://github.com/jsdom/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](https://www.npmjs.com/package/browser-env) to setup this fake environment.

```sh
npm install -D browser-env@3.3.0
```

And now we create a setup script.

```javascript
// ./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
browserEnv();

// 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.

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

TAP version 13
# counter increments
ok 1 should be equal

1..1
# 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 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.

```sh
npm install -D webpack@4.46.0 webpack-cli@4.6.0 webpack-dev-server@3.11.2 html-webpack-plugin@4.5.2
```

Let the config begins...

```javascript
// ./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
- [Write blazing fast Vue unit tests with Tape and Vue Test Utils](https://medium.freecodecamp.org/how-to-write-blazing-fast-vue-unit-tests-with-tape-and-vue-test-utils-be069ccd4acf)
- [learn tape](https://github.com/dwyl/learn-tape)

---

Thank you for reading. If you find this article useful and want to support my efforts, [buy me a coffee ☕](https://www.buymeacoffee.com/vonheikemen).

[![buy me a coffee](https://res.cloudinary.com/vonheikemen/image/upload/v1618466522/buy-me-coffee_ah0uzh.png)](https://www.buymeacoffee.com/vonheikemen)