Testing JavaScript

Agenda

  • Testing Pyramid
  • Definitions for Frontend
  • Static Quality Checking
  • Test Frameworks
  • Unit Tests
    • ES6 Class
      • DEMO
    • DOM Manipulation
      • DEMO
  • Integration Tests
    • Component Testing
    • dom-testing-library
      • DEMO
  • Ressources

Testing Pyramid

Definition for Frontend Apps

Unit Tests

individual and isolated components or functions work as expected

Integration Tests

several components work together in harmony, mock as little as possible (i.e. only mock network requests and animation libraries)

E2E Tests

A robot that clicks through the app and verifies behaviour.

Static Qualitychecks

Catch typos and type errors as you write the code

Static Quality checking

Code Style & Code Quality

Typos

Autofixing

These two work well together!

Type errors

Missing parameters

etc..

TSLint will be deprecated in favour of ESLint.

Test Frameworks

Jasmine

around since 2009

comes with assertions, mocking and spying tools

faster for smaller projects

Mocha + Chai + Sinon

around since 2011

Mocha = Test Runner, Chai = Assertion librar, Sinon = Stubs, Spys, Mocks

harder to set up, more configuration needed

allows for more control and customization

Jest

open sourced in 2014

everything out of the box for Vanilla- and DOM-testing (jsdom)

initially based on Jasmine => similar API

snapshot testing, parallel execution -> fast for big projects

What shall be used?

Jest

Recommendation:

Reasons:

 

Easy to setup and use

 

Fast (parallel execution)

 

DOM Testing out of the box

 

Features like mocking, coverage, snapshot testing out of the box

 

Maintained and used by Facebook

 

Big and growing community

 

Jest 24k

Mocha 17k 

Jasmine 14k

Github Stars

npm trends

Unit Tests

class Cow {
  constructor(name) {
    this.name = name || "Anon cow";
  }

  greets(target) {
    if (!target) throw new Error("missing target");
    return this.name + " greets " + target;
  }
}

export default Cow;

ES6 Class

import Cow from "../Cow";

describe("#greets", function() {
  it("should throw Error if no target is passed in", () => {
    expect(() => new Cow().greets()).toThrow(Error);
  });

  it("should greet passed target", () => {
    const greetings = new Cow("Kate").greets("Baby");
    expect(greetings).toEqual("Kate greets Baby");
  });
});

Jest

DEMO

import $ from "jquery";
import fetchCurrentUser from "./fetchCurrentUser.js";

/**
 * jQuery Plugin for displaying a User
 */
function displayUser() {
  fetchCurrentUser(user => {
    const { loggedIn, fullName } = user;
    const loggedText = "Logged " + (loggedIn ? "In" : "Out");
    $(this).text(fullName + " - " + loggedText);
  });
}

$.fn.displayUser = displayUser;

export default displayUser;

DOM Manipuation

e.g. through jQuery

JSDOM

pure JavaScript Implementation of web standards (DOM, HTML) for usage with Node.js

  • testing
  • scraping real-world web applications

JSDOM

// Construct HTML as String
const htmlString = `
    <!DOCTYPE html>
    <p>Hello world</p>
`

// Create DOM object with JSDOM
const dom = new JSDOM(htmlString);


// Use standard JavaScript API on DOM object as you would in a browser
console.log(dom.window.document.querySelector("p").textContent); 
// => "Hello world"

DEMO

Integration Tests

Component Testing

Button

Image

Product

Drop-down

SizeSelector

ProductInfo

Component Testing

Component Testing

Button

Image

Product

Drop-down

SizeSelector

ProductInfo

  • # of components > # of functions
  • unit tests not very likely to break
    • isolate the component too much from the rest of the application
  • ​# of integration tests > # of unit tests

dom-testing-library

  • API encourages good testing practices
    • avoids testing implementation details
  • comes in many flavours
    • vue-testing-library, react-testing-library, etc...

 

DOM Testing Abstraction

DOM Testing Library

querying DOM nodes in a way that's similar to how the user finds elements on the page​ 

// By Text
const submitButton = getByText(container, /send data/i)

// By Label Text
const inputNode = getByLabelText(container, 'Username')

// By Placeholder Text
const inputNode = getByPlaceholderText(container, 'Username')

// By Alt Text
const incrediblesPosterImg = getByAltText(container, /incredibles.*poster$/i)

// By Title
const deleteElement = getByTitle(container, 'Delete')

// By Display Value 
const lastNameInput = getByDisplayValue(container, 'Norris')

// By Role
const dialogContainer = getByRole(container, 'dialog')

// By Test ID
const usernameInputElement = getByTestId(container, 'username-input')

DEMO

Vue Testing Library

<template>
  <div>
    <span data-testid="test1">Hello World</span>
  </div>
</template>
// src/TestComponent.spec.js
import 'jest-dom/extend-expect'
import { render } from 'vue-testing-library'
import TestComponent from './TestComponent'

describe("TestComponent", () => {
    it('renders Hello World', () => {
      // render() returns several query* and get* methods
      const { queryByTestId } = render(TestComponent)
    
      // jest-dom brings more matchers like toHaveTextContent
      expect(queryByTestId('test1')).toHaveTextContent('Hello World')
    });
});

./src/TestComponent.vue

./src/TestComponent.spec.js

Feature: Sum a Pair
  It sums a pair of numbers
 
  Scenario: adds 1 + 2 to equal 3
    Given 1
    When add 2
    Then the sum is 3

jest-cucumber

defineFeature(feature, test => {
  test('adds 1 + 2 to equal 3', ({ given, when, then }) => {
    let x: number;
    let z: number;
    
    given('1', () => {
      x = 1;
    });
    
    when('add 2', () => {
      z = sum(x, 2);
    });
    
    then('the sum is 3', () => {
      expect(z).toBe(3);
    });
  });
});

Questions?

Ressources

Links & Documentation

 

Articles

AssertJS Talks

Establishing testing patterns with software design principles

Please don't mock me

Write tests. Not too many. Mostly integration.

Working Well: The Future of JavaScript Testing

Delightful JavaScript Testing with Jest

Copy of deck

By Gerald Urschitz