Testing JavaScript
Agenda
- Testing Pyramid
- Definitions for Frontend
- Static Quality Checking
- Test Frameworks
- Unit Tests
- ES6 Class
- DEMO
- DOM Manipulation
- DEMO
- ES6 Class
- 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
Copy of deck
- 276