JavaScript Unit tests

This article is about writing unit tests for non component stuff(models, reducers, actions, selectors).


Rules:

  • Adhere to the F.I.R.S.T. principles
  • Avoid complicated logic. Try to keep tests as simple as possible
  • Use beforeEach for common logic for 2 or more test cases
  • 1 expect per 1 test case(it)
  • Place, variables inside describe section
  • Indicate the type of the tested file


Test template
/ [START] describe global variables /
/* global describe, it */
/ [END] describe global variables /

/ [START] import vendors /
import { expect } from 'chai';
/ [END] import vendors /

/ [START] import common stuff /
import {
 isVersionCompatible,
 compareVersions
} from 'src/tests/helpers';
/ [END] import common stuff /

/ [START] import local stuff /
import {
 getEntities,
} from './model';
/ [END] import local stuff /

/ [START] describe 1 level /
describe('(Type) mergeAttributes', () => {
 / [START] variables for describe 1 level /
 const existing = {
 description: 'description'
 };
 / [END] variables for describe 1 level /

 / [START] optional logic to be performed before each test case /
 beforeEach(() => {});
 / [END] optional logic to be performed before each test case /

 / [START] tests for describe 1 level /
 it('should replace existing data with incoming data', () => {
 / [START] variables for test /
 const incoming = {
 description: 'no, this description',
 isSelected: true
 };
 / [END] variables for test /
 const actual = mergeAttributes(existing, incoming).description;
 const expected = 'no, this description';

 expect(actual).to.equal(expected);
 });
 / [END] tests for describe 1 level /

 / [START] describe 2 level /
 describe('merging existing data with incomplete incoming data', () {
 / [START] variables for describe 2 level [END] /

 / [START] tests for describe 2 level /
 / ... /
 / [END] tests for describe 2 level /
 });
 / [END] describe 2 level /

 / Other describe 2 level /
});
/ [END] describe 1 level /


Synthetic example to show how to mock external dependencies

Model file

An example of model file which generates book samples
import createBookSample from 'core';

const generateBookSamplesCreator = (createBook) => ({quantity, publisher}) => {
  let currentQuantity = quantity;
  let books = [];

  while (currentQuantity !== 0) {
    books.push(createBook({publisher}));
    currentQuantity--;
  }

  return books;
};

export const generateBookSamples = generateBookSamplesCreator(createBookSample);

Unit test for above-named file

Unit test for above-named file
import sinon from 'sinon';

import { generateBookSamplesCreator } from './generateBookSamples';

describe('(Model) generateBookSamples', () => {
  const book = {
    title: 'random generated title',
    description: 'random generated title',
    pagesNumber: 'random generated pages number',
  };
  const createBookSample = sinon.stub().returns(book);
  const generateBooks = generateBookSamplesCreator(createBookSample);

  const quantity = 25;
  const publisher = 'English house';
  let bookSamples;

  beforeEach(() => {
    bookSamples = generateBooks({quantity, publisher});
	createBookSample.resetHistory();
  });

  it('should create 25 books', () => {
    expect(bookSamples).to.have.lengthOf(quantity);
  });

  it('should pass publisher name to the "book creator"', () => {
    expect(createBookSample.args).to.equal([{ publisher }]);
  });
});