JavaScript unit testing met Jasmine

July 7th, 2013 | 7 min read | Jasmine, JavaScript, Testing, Web

Een tijd geleden (rond Januari) heb ik enkele tutorials geschreven over hoe je Java code kan unit testen. In deze tutorial is het tijd om eens een andere taal te unit testen, namelijk JavaScript. Voor JavaScript heb je een groot aanbod aan unit testing frameworks waar momenteel (jammer misschien) nog niet teveel standaarden rond zijn.

Enkele bekende JavaScript unit testing frameworks zijn:

  • JSUnit
  • QUnit
  • Jasmine
  • Expresso

JSUnit is echter enkel beschikbaar in een web browser terwijl Expresso dan weer enkel beschikbaar is voor Node.js tests. QUnit en Jasmine zijn daarmee toch de meest voor de hand liggende frameworks als je zowel front-end als back-end JavaScript code moet testen zonder dat je verschillende frameworks nodig hebt.

Persoonlijk vind ik Jasmine iets handiger als het gaat om groepering (test suits). Jasmine houdt het mooi asynchroon terwijl voor QUnit er ook wel groepering bestaat, maar daar raak je volgens mij snel de draad kwijt van welke test cases bij welke test suit horen en welke niet. Jasmine is trouwens geschreven door Pivotal labs die naast Jasmine ook unit testing frameworks heeft voor Android en Objective-C.

Een eenvoudig voorbeeld

In dit eerste voorbeeld ga ik tonen hoe je een unit test kan opbouwen. We gaan namelijk een eenvoudige library schrijven die twee getallen kan optellen, aftrekken, vermenigvuldigen en delen. Deze code gaan we dan nadien testen met Jasmine.

Als eerste stap zal je dus een web project moeten aanmaken met daarin een HTML pagina. Maak nadien een map assets/js aan en maak daarin het bestand math.js. In dit bestand kunnen we het volgende plaatsen:

/**
 * @author g00glen00b
 */

function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

function multiply(a, b) {
    return a * b;
}

function divide(a, b) {
    if (b == 0) {
        throw new TypeError("The second parameter cannot be zero");
    } else {
        return a / b;
    }
}

Nu moeten we enkel nog de HTML pagina aanpassen zodat we de math library kunnnen gebruiken.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />

        <!-- Always force latest IE rendering engine (even in intranet) & Chrome Frame
        Remove this if you use the .htaccess -->
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

        <title>Jasmine unit testing</title>
        <meta name="description" content="" />
        <meta name="author" content="g00glen00b" />

        <meta name="viewport" content="width=device-width; initial-scale=1.0" />

        <script type="text/javascript" src="assets/js/math.js"></script>
        <script type="text/javascript">
            console.log("5 + 3 = " + add(5, 3));
            console.log("5 - 3 = " + subtract(5, 3));
            console.log("5 * 3 = " + multiply(5, 3));
            console.log("5 / 3 = " + divide(5, 3));
            console.log("5 / 3 = " + divide(5, 0));
        </script>
    </head>

    <body>

    </body>
</html>

Als we nu de HTML pagina zouden openen, kregen we in onze console het volgende te zien.

math-first-test

Unit testing

We kunnen ook de code gaan unit testen met Jasmine. Hiervoor hebben we uiteraard eerst de juiste JavaScript library nodig. Deze kunnen we toevoegen met:

<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine-html.js"></script>

<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jasmine/1.3.1/jasmine.css" />

De eerste library is het unit testing framework zelf, de andere twee (JavaScript + CSS) zorgen ervoor dat je een HTML report kan genereren. Als je een Node.js project zou gaan testen heb je deze uiteraard niet nodig.

We gaan nu ook nog in de map assets/js een nieuw JavaScript bestand aanmaken met de naam tests.js. Dit voegen we onderaan de

<head>

van onze HTML pagina toe.

Nu kunnen we beginnen met het schrijven van unit tests in

tests.js

. Het eerste wat we nodig hebben is een test suit, deze kan je maken door middel van:

describe("Math", function() {

});

Met deze code hebben we een test suit met de naam “Math” aangemaakt. De volgende stap is dat we een test case aanmaken, bijvoorbeeld:

it("add", function() {

});

Hiermee hebben we een test case aangemaakt met de naam “add”. Nu kunnen we onze code gaan testen.

describe("Math", function() {
    it("add", function() {
        expect(add(5, 3)).toBe(8);
        expect(add(5, -3)).toBe(2);
        expect(add(-5, 3)).toBe(-2);
        expect(add(-5, -3)).toBe(-8);
        expect(add(5, 0)).toBe(5);
        expect(add(0, 5)).toBe(5);
        expect(add(0, 0)).toBe(0);
    });
});

Dit is misschien een beetje overkill, maar met deze test case hebben we enkele scenario’s uitgewerkt (negatieve getallen, positieve getallen en 0). Een waarde vergelijken kan je dus doen met:

expect(...).toBe(...);

Let er wel op dat dit enkel geldt voor primitieve waardes, indien je arrays of objecten wenst te vergelijken gebruik je:

expect(...).toEqual(...);

Er zijn ook enkele shortcuts voor booleans en null, bijvoorbeeld:

expect(...).toBeTruthy();
expect(...).toBeFalsy();
expect(...).toBeNull();

Meer kan je uiteraard altijd nalezen op de website zelf. Nu kunnen we hetzelfde doen voor de andere functies.

describe("Math", function() {
    it("add", function() {
        expect(add(5, 3)).toBe(8);
        expect(add(5, -3)).toBe(2);
        expect(add(-5, 3)).toBe(-2);
        expect(add(-5, -3)).toBe(-8);
        expect(add(5, 0)).toBe(5);
        expect(add(0, 5)).toBe(5);
        expect(add(0, 0)).toBe(0);
    });

    it("subtract", function() {
        expect(subtract(5, 3)).toBe(2);
        expect(subtract(5, -3)).toBe(8);
        expect(subtract(-5, 3)).toBe(-8);
        expect(subtract(-5, -3)).toBe(-2);
        expect(subtract(5, 0)).toBe(5);
        expect(subtract(0, 5)).toBe(-5);
        expect(subtract(0, 0)).toBe(0);
    });

    it("multiply", function() {
        expect(multiply(5, 3)).toBe(15);
        expect(multiply(5, -3)).toBe(-15);
        expect(multiply(-5, 3)).toBe(-15);
        expect(multiply(-5, -3)).toBe(15);
        expect(multiply(5, 0)).toBe(0);
        expect(multiply(0, 5)).toBe(0);
        expect(multiply(0, 0)).toBe(0);
    });

    it("divide", function() {
        expect(divide(5, 2)).toBe(2.5);
        expect(divide(5, -2)).toBe(-2.5);
        expect(divide(-5, 2)).toBe(-2.5);
        expect(divide(-5, -2)).toBe(2.5);
        expect(divide(0, 5)).toBe(0);
    });
});

Zoals je kan zien zijn alle tests vrij gelijkaardig. Enkel bij “divide” heb ik enkele test scenario’s weg gelaten omdat we hier eigenlijk twee code branches hebben, namelijk als we delen door 0 en een andere branch voor als we niet delen door 0. De bovenstaande test case is enkel voor deze tweede branch. Om de andere branch te testen moeten we eigenlijk testen of er een exception gethrowed wordt of niet. Dit doen we met:

it("divide by zero", function() {
    expect(function() {
            divide(5, 0);
    }).toThrow();
    expect(function() {
            divide(0, 0);
    }).toThrow();
});

Zoals je kan zien gebruiken we hier

expect(...).toThrow();

Wat hierbij wel héél belangrijk is, is dat je de functie die de exception (kan) throwen altijd wrapt in een anonieme functie. Doe je dat niet, dan gaat de error niet opgevangen worden door het testing framework. Dus eigenlijk is de syntax meer iets als:

expect(function() { ... }).toThrow();

Test reporter

We hebben nu alle test cases opgesteld die we wensen te testen. We hebben alleen nog geen reporter gedefinieerd. In dit voorbeeld ga ik, omdat we toch gebruik maken van een web project, de HTML reporter gebruiken. Plaats de volgende code onderaan je tests:

var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);

jasmineEnv.specFilter = function(spec) {
    return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
    if (currentWindowOnload) {
        currentWindowOnload();
    }
    jasmineEnv.execute();
};

Als we nu de HTML pagina openen, krijgen we iets als in onderstaande afbeelding te zien.

math-jasmine

Stel dat we nu iets aanpassen in onze tests waardoor deze niet meer klopt, dan krijgen we het volgende te zien.

math-jasmine-fail

Daarmee hebben we dan ook deze tutorial afgerond.

Download project

Download – jasmine-example.tar.gz (2 kB)
Demo

Back to tutorialsContact me on TwitterDiscuss on Twitter

Profile picture

Dimitri "g00glen00b" Mestdagh is a consultant at Cronos and tech lead at Aquafin. Usually you can find him trying out new libraries and technologies. Loves both Java and JavaScript.