Javascript Testing Cheat Sheet

5 minute read

The following is a series of testing frameworks and libraries suggestions for JS/React projects, basic syntax and usage, as well as pointers to official sites and documentation.

The suggestions are based on the following Udemy course: https://www.udemy.com/js-com-tdd-na-pratica/ (in portuguese) created by Willian Justen.

NPM packages:

npm i --save-dev mocha chai sinon sinon-chai enzyme chai-enzyme node-fetch jsdom jsdom-global nyc coveralls

Quick rundown of each package:

Package Usefulness
Mocha Test runner, provides describe, it, context, beforeEach, etc.
Chai Assertion library, provides expect, to, have, be, a, an, etc.
Sinon Test spies, stubs and mocks, provides sinon.stub, etc.
SinonChai Integration between Sinon and Chai, provides calledWith, calledOn, etc.
Enzyme Assertion library for testing React components.
ChaiEnzyme Integration between Chai and Enzyme, provides shallow, mount, render, etc.
Node-Fetch Brings window.fetch to Node.js, allowing to test fetch calls, etc.
jsdom/global Provides access to document and window elements on Node.js for testing.
NYC Test coverage report
Coveralls Code coverage insight

Mocha

Official site and Documentation

Syntax example:

describe('componentName', () => {
  context('givenContext', () => {
    it('should meet this expectation...', () => {
      ...
    });
  });
});

Usage example:

describe('<FullHeader />', () => {
   it('should have header tag when mount', () => {
        const wrapper = shallow(<FullHeader />);
        expect(wrapper.find('header')).to.have.length(1);
   });

   context('title', () => {
       it('should have h1 tag when title passed', () => {
           const wrapper = shallow(<FullHeader title="TDD"/>);
           expect(wrapper.find('h1')).to.have.length(1);
       });
   });
});

Chai

Official site and Documentation

Syntax example:

import chai, { expect } from 'chai';

expect(object).to.exist;
expect(object).to.have.length(5);
expect(object).to.be.equal('foo');

Usage example:

import { expect } from 'chai';
import converToHumanTime from '../src/ConvertToHumanTime';

describe('ConvertToHumanTime', () => {
  // 0ms === 0:00
  it('should receive 0ms and convert to 0:00', () => {
    expect(converToHumanTime(0)).to.be.equal('0:00');
  });

  // 1000ms === 0:01
  it('should receive 1000ms and convert to 0:01', () => {
    expect(converToHumanTime(1000)).to.be.equal('0:01');
  });

  // 11000 === 0:11
  it('should receive 11000 and convert to 0:11', () => {
    expect(converToHumanTime(11000)).to.be.equal('0:11');
  });

  // 60000 === 1:00
  it('should receive 60000 and convert to 1:00', () => {
    expect(converToHumanTime(60000)).to.be.equal('1:00');
  });
});

Sinon

Official site and Documentation

Syntax example:

import sinon from 'sinon';

let consoleStub;
// before usage
consoleStub = sinon.stub(console, 'info');
// after usage, clearing modified state
consoleStub.restore();

Usage example:

import sinon from 'sinon';
global.fetch = require('node-fetch');

let stubbedFetch;
let promise;

beforeEach(() => {
  stubbedFetch = sinon.stub(global, 'fetch');
  promise = stubbedFetch.resolves({ json: () => ({ album: 'name' }) });
});

afterEach(() => {
  stubbedFetch.restore();
});

Sinon Chai

Official site and Documentation

Syntax example:

import chai, { expect } from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';

chai.use(sinonChai)

expect(mySpy).to.have.been.calledWith("foo");
expect(mySpy).to.have.been.calledOnce;
expect(mySpy).to.have.been.called;

Usage example:

import chai, { expect } from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';

chai.use(sinonChai)

global.fetch = require('node-fetch');

describe('Album', () => {
  let spotify;
  let stubbedFetch;
  let promise;

  beforeEach(() => {
    spotify = new SpotifyWrapper({
      token: 'foo',
    });

    stubbedFetch = sinon.stub(global, 'fetch');
    promise = stubbedFetch.resolves({ json: () => ({ album: 'name' }) });
  });

  afterEach(() => {
    stubbedFetch.restore();
  });
  
  describe('getTracks', () => {

    it('should call fetch method', () => {
      const albumTrack = spotify.album.getTracks();
      expect(stubbedFetch).to.have.been.calledOnce;
    });

    it('should call the correct URL', () => {
      const albumTrack = spotify.album.getTracks('6akEvsycLGftJxYudPjmqK')
      expect(stubbedFetch).to.have.been.calledWith('https://api.spotify.com/v1/albums/6akEvsycLGftJxYudPjmqK/tracks')
    });

    it('should return the correct data frin Promise', () => {
      const tracks = spotify.album.getTracks('6akEvsycLGftJxYudPjmqK');
      tracks.then((data) => {
        expect(data).to.be.eql({ album: 'name' });
      });
    });
  });

Enzyme

Official site and Documentation

Syntax example:

import React from 'react';
import { shallow } from 'enzyme';

const wrapper = shallow(<MyComponent />);
const wrapper = shallow((
    <MyComponent>
        <div className="unique" />
    </MyComponent>
));

Usage example:

import React from 'react';
import { expect } from 'chai';
import { render } from 'enzyme';

import Foo from './Foo';

describe('<Foo />', () => {
  it('renders three `.foo-bar`s', () => {
    const wrapper = render(<Foo />);
    expect(wrapper.find('.foo-bar')).to.have.lengthOf(3);
  });
});

Enzyme Chai

Official site and Documentation

Syntax example:

const wrapper = mount(<Fixture />); // mount/render/shallow when applicable

expect(wrapper.find('#checked')).to.be.checked();

expect(wrapper.find('span')).to.have.className('child');

expect(wrapper).to.contain(<User index={1} />);
expect(wrapper).to.contain([<User index={2} />, <User index={3} />]);


expect(wrapper).to.containMatchingElement(<User name='John' />);

Usage example:

import React from 'react';
import chai, { expect } from 'chai';
import chaiEnzyme from 'chai-enzyme';
import { shallow } from 'enzyme';
import FullHeader from '../../src/Main';

chai.use(chaiEnzyme());

describe('<FullHeader />', () => {
   it('should have header tag when mount', () => {
        const wrapper = shallow(<FullHeader />);
        expect(wrapper.find('header')).to.have.length(1);
   });

   context('title', () => {
       it('should have h1 tag when title passed', () => {
           const wrapper = shallow(<FullHeader title="TDD"/>);
           expect(wrapper.find('h1')).to.have.length(1);
       });

       it('should not have h1 tag when title is not passed', () => {
           const wrapper = shallow(<FullHeader />);
           expect(wrapper.find('h1')).to.have.length(0);
       });

       it('should have h1 tag with the title passed', () => {
           const wrapper = shallow(<FullHeader title="TDD" />);
           expect(wrapper.find('h1').props().children).to.be.equal("TDD");
       });
   });

Node Fetch

Official site and Documentation

Syntax example:

global.fetch = require('node-fetch');

// plain text or html
fetch('https://github.com/')
	.then(res => res.text())
	.then(body => console.log(body));

// json
fetch('https://api.github.com/users/github')
	.then(res => res.json())
	.then(json => console.log(json));

Usage example:

import chai, { expect } from 'chai';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';

chai.use(sinonChai);

global.fetch = require('node-fetch');

let spotify;
  let stubbedFetch;

  beforeEach(() => {
    spotify = new SpotifyWrapper({
      token: 'foo'
    });
    stubbedFetch = sinon.stub(global, 'fetch');
    stubbedFetch.resolves({ json: () => {} });
  });

  afterEach(() => {
    stubbedFetch.restore();
  });

describe('spotify.search.artists', () => {
    it('should call fetch function', () => {
      const artists = spotify.search.artists('Incubus');
      expect(stubbedFetch).to.have.been.calledOnce;
    });

    it('should fetch with the correct URL', () => {
      const artists = spotify.search.artists('Incubus');
      expect(stubbedFetch).to.have.been.calledWith('https://api.spotify.com/v1/search?q=Incubus&type=artist');

      const artists2 = spotify.search.artists('Muse');
      expect(stubbedFetch).to.have.been.calledWith('https://api.spotify.com/v1/search?q=Muse&type=artist');
    });
});

JSDOM/ jsdom-global

Official site and Documentation

Syntax example:

const jsdom = require("jsdom");
const { JSDOM } = jsdom;

const dom = new JSDOM(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent); // "Hello world"

Usage example:

import 'jsdom-global/register';
import chai, { expect } from 'chai';

 it('should create an instance of SpotifyWrapper', () => {
    const spotify = new SpotifyWrapper({});
    expect(spotify).to.be.an.instanceof(SpotifyWrapper);
  });
  
  it('should receive apiURL as an option', () => {
    const spotify = new SpotifyWrapper({
      apiURL: 'blabla',
    });

    expect(spotify.apiURL).to.be.equal('blabla');
  });
  
   const markup = `
    <img class="album-image" src="https://i.scdn.co/image/59a536f0bf0ddaa427e4c732a061c33fe7578757" alt="The Essential Incubus">
    <p class="album-title">The Essential Incubus</p>
    <p class="album-artist">Incubus</p>
    <p class="album-counter">18 Músicas</p>
  `;

  it('should create and append the markup given a correct data', () => {
    const element = document.createElement('div');
    renderAlbumInfo(data, element);

    expect(element.innerHTML).to.be.eql(markup);
  });

NYC and Coveralls

NYC Official site and Documentation

Coveralls Official site and Documentation

Output example:

-----------|----------|----------|----------|----------|-------------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
-----------|----------|----------|----------|----------|-------------------|
All files  |      100 |      100 |      100 |      100 |                   |
 album.js  |      100 |      100 |      100 |      100 |                   |
 config.js |      100 |      100 |      100 |      100 |                   |
 index.js  |      100 |      100 |      100 |      100 |                   |
 search.js |      100 |      100 |      100 |      100 |                   |
 utils.js  |      100 |      100 |      100 |      100 |                   |
-----------|----------|----------|----------|----------|-------------------|

Usage example:

## package.json
{
  ...
  "scripts": {
    ...
    "test": "./node_modules/.bin/mocha tests/**/*.spec.js",
    "test:tdd": "./node_modules/.bin/mocha tests/**/*.spec.js --watch",
    "test:coverage": "nyc npm test",
    "coveralls": "npm run test:coverage && nyc report --reporter=text-lcov | coveralls"
  },
  "files": [
    "dist",
    "lib"
  ],
  "nyc": {
    "reporter": [
      "text",
      "html"
    ],
    "exclude": [
      "tests/**"
    ]
  },
  ...
}

## terminal
# for basic coverage
npm run test:coverage

# for coveralls submission
npm run coveralls

Updated:

Leave a Comment