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.