Testujeme React komponenty s Karmou a Enzyme

Není nad to, testovat front-endové aplikace nejlépe na několika prohlížečích současně. Karma to zvládne automaticky a Enzyme celé testování zjednoduší.

TL;DR

  • Frontendový kód je třeba testovat přímo v jeho prostředí: v prohlížečích. To řeší Karma.
  • Testujeme už transpilovaný kód, který je co možná nejpodobnější produkčnímu kódu. Do procesu je třeba zapojit Webpack
  • K ověření správných výsledků a simulaci událostí pomůže Enzyme od AirBnB
  • Debugujeme na běžícím Karma serveru za pomoci direktivy debugger;
  • Karma není určena k end-to-end testům (od toho je Selenium), proto je vhodné dotazy na server mockovat pomocí balíčku Sinon
  • Zapojte do testů další prohlížeče!

Je důležité mít Karmu

Stejně jako spouštíte testy serverové logiky v Node.js je vhodné testy front-endového kódu spouštět přímo na prohlížeči a ideálně na všech nejpoužívanějších. To vše za vás zvádne automaticky Karma.

Karma je frontendový test-runner, který vezme testy z vašeho oblíbeného testovacího frameworku (Mocha nebo Jasmine), připraví je prostřednictvím vámi zvoleného preprocesoru (Babel) pro spuštění a následně je ve vámi definovaných prohlížečích spustí.

Výhody spouštění testů přímo v prohlížeči jsou nesporné: Nepřekvapí vás různá interpretace kódu na různých browserech. To se týká jak validace vašeho kódu, tak i kódu, který generuje transpiler (Babel), který překládá váš ES6 kód do ES5.

Zapojujeme Karmu do projektu s Webpackem

Pujčil jsem si zdrojový kód z předchozího příkladu o zapojení ESLintu do Node.js/React projektu. Aby testy Karmou fungovaly, je třeba si nainstalovat Karmu, Mochu, Chrome a Enzyme včetně testovacích nástrojů od Reactu.

npm i -D karma karma-cli karma-mocha karma-webpack mocha karma-chrome-launcher karma-spec-reporter react-addons-test-utils enzyme  

Pokud bude vaše projektová struktura následující:

- /
  ├── /lib
  ├── /public
  |   ├── /components
  |   └── /lib
  └── /tests
      ├── /lib
      └── /public
          ├── /components
          └── /lib

Je nutné v kořenu aplikace vytvořit konfigurační soubor pro Karmu /karma.config.js, který využívá produkční nastavení webpacku /webpack.config.js a doplňuje do něj potřebná nastavení pro Karmu a React. (zdrojový kód konfigurace Webpacku je k dispozici zde).

require('webpack');  
const webpackConfig = require('./webpack.config');

module.exports = function karmaConfig (config) {  
    config.set({
        browsers: ['Chrome'],
        singleRun: true,
        frameworks: ['mocha'],
        files: [
            'webpack.tests.js'
        ],
        preprocessors: {
            'webpack.tests.js': ['webpack']
        },
        reporters: ['dots'],
        webpack: Object.assign({}, webpackConfig, {
            devtool: 'inline-source-map',
            watch: true,
            externals: {
                cheerio: 'window',
                'react/lib/ExecutionEnvironment': true,
                'react/lib/ReactContext': true,
                'react/addons': true
            }
        }),
        webpackServer: {
            noInfo: true
        }
    });
};

A je třeba do souboru /webpack.tests.js připravit loader testů, který načte pouze frontendové testy ze složky /test/public:

const context = require.context('./test/public', true, /\.jsx?$/);  
context.keys().forEach(context);  

Spouštíme a ladíme testy pomocí NPM

Pro jednoduché spuštění testů stačí do souboru package.json následující řádky, které zajístí jednorázové spuštění Karmy příkazem: npm run karma.

  "scripts": {
    "test": "npm run karma",
    "karma": "./node_modules/karma/bin/karma start",
    "karma:debug": "./node_modules/karma/bin/karma start --browsers=Chrome --single-run=false"
  },

Příkaz npm run karma:debug pak spustí Karma server pro ladění. Direktiva watch: true zajistí, že se změna v testu, nebo ve zdrojovém kódu promítne do spuštěného prohlížeče a nebudete muset pokaždé čekat na spuštění Karmy.

Aby se vám v Chrome konzoli lépe hledal kus kódu, který chcete ladit, vložte do něj místo breakpointu direktivu debugger;. Chrome se vám sám zastaví na místě, které potřebujete odladit.

Píšeme test s Enzyme

Enzyme je testovací nástroj pro React od AirBnB. Jeho výhody spočívají jednoduhém assertování (ověřování, zda výsledný stav odpovídá očekávání) a simulaci událostí. Jeho použití ilustruje následující příklad. Mějme jednoduchou komponentu, která po stisku tlačítka přepne svůj vnitřní stav a zobrazí element <b></b>:

const React = require('react');

class App extends React.Component {

    constructor (props) {
        super(props);
        this.state = {
            shown: false
        };
        this.buttonPressed = this.buttonPressed.bind(this);
    }

    buttonPressed () {
        this.setState({ shown: !this.state.shown });
    }

    render () {
        let shown = null;
        if (this.state.shown) {
            shown = (<b>Visible!</b>);
        }
        return (<div>
            <h2>Hello world</h2>
            <input type="button" onClick={this.buttonPressed} value="Muh" />
            {shown}
        </div>);
    }
}

module.exports = App;  

Chceme li otestovat, zda se komponenta správně vykresluje, je třeba ji neprve vyrenderovat. Enzyme k tomuto poskytuje dvě metody. První, shallow() slouží spíše čistě jednotkovým testům, protože na rozdíl od metody mount() nezapojí do testování DOM prohlížeče. Jednoduchý test, který ověří vypsání správného titulku, vypadá následovně:

const assert = require('assert');  
const App = require('../../../public/components/app.jsx');  
const React = require('react');  
const enzyme = require('enzyme');

describe('#app component', () => {  
    it('should render header', () => {
        const app = enzyme.mount(
            <App />
        );
        assert.equal(app.find('h2').text(), 'Hello world');
    });
});

Aby bylo možné testovat naplno, je třeba vyvolávat události, například kliky. K tomu stačí metodou find() najít element, na kterém potřebujete událost vyvolat a metodou simulate() samotnou akci provést. Zbývá pak jen otestovat, zda výsledek odpovídá realitě a v tomto případě se testuje přítomnost elementu <b></b>, který je zobrazován a skrýván klikem na tlačítko.

it('should toggle text', () => {  
    const app = enzyme.mount(
        <App />
    );

    app.find('input').simulate('click');
    assert.equal(app.find('b').length, 1, 'text should be visible after first click');

    app.find('input').simulate('click');
    assert.equal(app.find('b').length, 0, 'text should be hidden after second click');
});

Karma není o end-to-end testování

Aby vám ušetřil přemýšlení, jak tímto způsobem testovat celou aplikaci, rovnou řeknu: Raději se o to nepokoušejte a použijte raději Selenium, které je pro tento účel vhodnější. Pokud se vám však některá aplikační logika na server dotazuje, použijte například Sinon, který překryje původní XMLHttpRequest.

Není nad přiklad

Celý funkční kód je k vidění na mém gitu v příkladu.

David Menger

Read more posts by this author.