Testen von React- und Flux-Anwendungen mit Karma und Webpack

reactflux1 3

reactflux
Bei Rescale nutzten wir die ist Framework von Facebook zum Testen unserer React- und Flux-Anwendung. Jest ist ein einfach zu startendes Test-Framework mit einer API ähnlich der von Jasmin, aber mit automatischem Mocking von CommonJS-Modulen. Wie viele Entwickler in der Open-Source-Community haben bekanntWir haben festgestellt, dass Jest bei der Ausführung einer nicht-trivialen Testsuite unerträglich langsam sein kann. Für unsere Testsuite mit etwa 60 Testfällen dauert es weit über 10 Sekunden, bis sie abgeschlossen ist! Dieser Artikel erklärt, wie wir ist gespendet zugunsten Karma, Webpack und Jasmin. Die Ausführung derselben Testsuite unter unserem neuen Setup dauert in PhantomJS nach der ersten Webpack-Bündelung nur knapp 200 ms.
Unser Testaufbau basiert auf dem in diesem Artikel erläuterten Aufbau: Testen von ReactJS-Komponenten mit Karma und Webpack. Lesen Sie das, wenn Sie mit Karma und Webpack nicht vertraut sind. Zusammenfassend lässt sich sagen, dass dieses Setup karma-webpack verwendet, um alle unsere Tests in einer einzigen Datei zu bündeln, die Karma lädt und im Browser ausführt. Die dafür benötigten npm-Pakete sind:

  • Karma
  • Karma-CLI
  • Karma-Jasmin
  • Karma-Phantomjs-Launcher
  • Karma-Webpack
  • Jasmin
  • Webpack
  • core-js (für ES5-Shims)
  • Babel-Loader (für die Transpilierung von ES6 nach ES5)

Die Karma-Konfigurationsdatei, karma.conf.js, sollte ungefähr so aussehen:

module.exports = function(config) { config.set({ Browser: ['PhantomJS'], Dateien: [ { Muster: 'tests.webpack.js', beobachtet: false }, ], Frameworks: ['Jasmine'], Präprozessoren: { 'tests.webpack.js': ['webpack'], }, Reporter: ['dots'], singleRun: true, webpack: { Modul: { Loader: [ { Test: /\.jsx?$/, ausschließen: /node_modules/, Loader: 'babel-loader' }, ], }, beobachten: true, }, webpackServer: { noInfo: true, }, }); };

Und der Einstiegspunkt für das Webpack-Bundle der Testsuite, tests.webpack.js, sollte ungefähr so aussehen:

// ES5-Shims für Function.prototype.bind, Object.prototype.keys usw. require('core-js/es5'); // Ersetzen Sie ./src/js durch das Verzeichnis Ihres Anwendungscodes und // stellen Sie sicher, dass der reguläre Ausdruck des Dateinamens mit Ihren Testdateien übereinstimmt. var context = require.context('./src/js', true, /-test\.js$/); context.keys().forEach(context);

Hier ist ein Beispieltest unter ./src/js/components/__tests__/MemberList-test.js, der die Komponente ./src/js/components/MemberList.jsx testet:

var React = require('react'); var TestUtils = require('react/lib/ReactTestUtils'); var MemberList = require('../MemberList.jsx'); beschreiben('MemberList', () => { it('renders', () => { var element = TestUtils.renderIntoDocument(); expect(element).toBeTruthy(); }); });

Das ist alles schön und gut zum Testen von React-Komponenten, aber das Testen von Flux-Anwendungen, insbesondere Flux Stores, erfordert etwas mehr Einrichtung.

Testen von Flux Stores

Das Problem mit Flux-Stores besteht darin, dass Stores normalerweise Singletons mit Status sind und Singletons mit Zustand können durchaus zu den schwierigsten Tests gehören, da ihr Zustand zwischen den Tests bestehen bleibt. Es wird schwierig, den Zustand des Stores vorherzusagen, wenn viele Teile unserer Anwendung während der gesamten Testsuite mit ihm interagieren. Man könnte meinen, man könnte einfach eine Methode implementieren, um den Zustand des Stores vor jedem Testfall zurückzusetzen. Dies für jeden Store zu tun, ist jedoch ein Albtraum für die Wartung und sehr fehleranfällig.
Jest löst dieses Problem, indem jeder Testfall in seiner eigenen Umgebung ausgeführt wird. Mit anderen Worten: Wenn wir in einem Testfall ein Modul benötigen, wird das importierte Modul als neue Instanz verwendet. Genau dieses Verhalten wünschen wir uns beim Testen von Stores.
Mit Webpack können wir genau das tun: Den Require-Cache vor jedem Testfall leeren, sodass Aufrufe von Require eine neue Instanz eines Moduls laden. Zur Optimierung sollten wir Drittanbietermodule wie React nicht aus dem Require-Cache entfernen. Dies würde unsere Testsuite erheblich verlangsamen, und keines dieser Drittanbietermodule sollte ohnehin Singletons mit Status sein. Wir können Cache-Busting vor jedem Test in der Datei tests.webpack.js wie folgt hinzufügen:

// Erstellen Sie einen Webpack-Anforderungskontext, damit wir die Module unseres // Projekts dynamisch anfordern können. Schließen Sie Testdateien in diesem Kontext aus. var projectContext = require.context('./src/js', true, /^((?!__tests__).)*.jsx?$/); // Extrahieren Sie die Modul-IDs, die Webpack zum Verfolgen von Modulen verwendet. var projectModuleIds = projectContext.keys().map(module => String(projectContext.resolve(module))); beforeEach(() => { // Entfernen Sie unsere Module vor jedem Testfall aus dem Anforderungscache. projectModuleIds.forEach(id => delete require.cache[id]); });

Das ist alles, was Sie zum Aufbrechen des erforderlichen Caches tun müssen. Jetzt werden Module innerhalb jedes Testfalls zwischengespeichert, aber nicht dazwischen. Stores, Aktionen und Dispatcher sind zwischen den Tests isoliert.

Testen der Aktions-/Speichergrenze

Wir verwenden Rückfluss als Flux-Implementierung für eines unserer Projekte, und Aktionen sind praktisch die öffentliche API für Stores. Das Auslösen einer Aktion ist jedoch ein asynchroner Vorgang, daher benötigen wir eine Möglichkeit, die Asynchronität in unseren Tests zu kontrollieren. Dies sollte mit jedem Testframework oder jeder Bibliothek, die die Möglichkeit bietet, die nativen Funktionen setTimeout und setInterval zu simulieren und manuell zu erweitern, problemlos möglich sein. Hier ist ein Beispiel für das Testen der Action/Store-Grenze mit Jasmine:

describe('MemberStore', () => { var MemberActions; var MemberStore; beforeEach(() => { jasmine.clock().install(); // Simuliere die integrierten Timer MemberActions = require('../../actions/MemberActions'); MemberStore = require('../MemberStore'); }); afterEach(() => { jasmine.clock().uninstall(); }); it('saves member when received', () => { MemberAction.memberReceived({ name: 'Baz Fu' }); jasmine.clock().tick(); // Stelle die Uhr auf den nächsten Tick vor expect(MemberStore.getAll().first().get('name')).toBe('Baz Fu'); }); });

Mocking der Abhängigkeiten eines Moduls

Angenommen, wir möchten ein Modul testen, das von einem anderen Modul abhängig ist, das wir simulieren möchten. Ein Beispiel könnte sein, dass wir ein API-Modul testen möchten, das von einem HTTP-Modul abhängt, wie z. B. Axios. Hier glänzt Jest, da es mit seiner API jest.setMock(moduleName, moduleExports) oder mit seinen automatischen Mocking-Funktionen einfach ist, Mocks anzugeben.
Eine Möglichkeit, dies außerhalb von Jest zu erreichen, ist die Verwendung verkabeln und seine Webpack-Version rewire-webpackMit Rewire können private Variablen in einem Modul geändert werden. Wenn wir beispielsweise das Axios-Modul in unserem api.js-Modul simulieren möchten, können wir Folgendes schreiben:

// api.js var axios = require('axios'); var API = { doSomething() { axios.get('https://someurl/'); }, }; module.exports = API; // In einigen Tests var API = rewire('../api'); API.__set__('axios', mockAxios);

Eine weitere Möglichkeit besteht darin, den erforderlichen Cache von Webpack erneut zu manipulieren:

require('axios'); // Stellen Sie sicher, dass das Modul geladen und zwischengespeichert ist require.cache[require.resolve('axios')].exports = mockAxios;

Der Vorteil gegenüber der Neuverdrahtung besteht darin, dass jeder Aufruf von require('axios') simuliert wird, nicht nur in dem Modul, das wir neu verdrahtet haben.
Hinweis: Das Mocking einer HTTP-Anforderungsbibliothek ist wahrscheinlich ein schlechtes Beispiel. Dafür sollten Sie etwas wie Jasmin-Ajax stattdessen, aber Sie verstehen, was ich meine.

Schließen

Wir konnten bei der Ausführung unserer React- und Flux-Anwendungstestsuite mit Karma und Webpack im Vergleich zu Jest eine etwa 50-fache Geschwindigkeitssteigerung feststellen. Allerdings erfordert die Einrichtung im Vergleich zu Jest etwas mehr Wissen und Aufwand, und das Mocking der Modulabhängigkeiten ist nicht so einfach. Jest ist aufgrund der einfachen Einrichtung sehr praktisch, aber die hohe Geschwindigkeit ist für uns ein K.O.-Kriterium, und wir wissen nicht, ob das jemals behoben wird. Bis dahin ermöglicht uns unser aktuelles Setup effektives TDD.