Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Unit Testing - React DC Meetup

Sami
November 18, 2014

React Unit Testing - React DC Meetup

High level over view of unit testing React/Require application with Jasmine and Karma.

Sami

November 18, 2014
Tweet

Other Decks in Programming

Transcript

  1. Overview 1. Unit testing philosophy 2. Quick overview of tools

    3. Setting up testing tools 4. React component testing 5. Flux store testing
  2. RequireJS • Javascript asynchronous module loader • Uses the AMD

    (Asynchronous Module Definition) API //my React class define(['react','reactComponent1','reactComponent2', ‘myStore’], function(React,Component1,Component2, MyStore){ var myReactComponent = React.createClass({ //Reacty stuff here that uses Component1, Component2 and MyStore }); return myReactComponent; });
  3. Jasmine • Jasmine is a Behavior-driven development (BDD) testing framework

    • Doesn’t require a DOM describe("My Billing Form", function() { it("should default form first name", function() { //use various matchers jasmine provides to assert results expect(myForm.firstName).toBe("myDefaultValue"); }); });
  4. Karma • Testing framework agnostic test-runner • Spawns a web

    server and executes test code against source code • Supports multiple browsers (Full List) • Displays results of tests (stating the obvious) • Plugs in well with several CI build tools
  5. Karma Setup • Essentially two files to worry about: -

    karma.conf.js - test-main.js - Reference Karma’s require page for full documentation
  6. karma.conf.js module.exports = function(config) { config.set({ // base path that

    will be used to resolve all patterns (eg. files, exclude) basePath: '', //points to current directory // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ['jasmine', 'requirejs'], // list of files / patterns to load in the browser files: [ {pattern: 'libs/**/*.js', included: false}, {pattern: 'app/**/*.js', included: false}, {pattern: 'test/**/*Spec.js', included: false}, 'test/main-test.js'], // list of files to exclude exclude: [ 'app/main.js', 'libs/require.js'], //available launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: [ 'PhantomJS', 'Chrome', 'Firefox'], //other properties below }); };
  7. test-main.js var allTestFiles = [], TEST_REGEXP = /Spec\.js$/, pathToModule =

    function(path) { return path.replace(/^\/base\//, '').replace(/\.js$/, ''); }; Object.keys(window.__karma__.files).forEach(function(file) { if (TEST_REGEXP.test(file)) { // Normalize paths to RequireJS module names. allTestFiles.push(pathToModule(file)); } }); require.config({ baseUrl: '/base/app', paths: { 'phantom-shim' : '../libs/phantomjs-shims', 'react': '../libs/plugins/react', }, shim: { react: { deps: ['phantom-shim'], exports: 'React' } }, // dynamically load all test files deps: allTestFiles, // we have to kickoff jasmine, as it is asynchronous callback: window.__karma__.start});
  8. MyLinkComponent.jsx define(['react'], function(React){ return React.createClass({ getInitialState: function(){ return { hasBeenClicked

    : false }; }, doSomething: function(){ this.setState({ hasBeenClicked : true }); }, render: function() { var div; if(this.props.isAdmin){ div = <a href={this.props.url} onClick={this.doSomething}>{this.props.text}</a>; } return <small className={'title-options'}>{div}</small> } }); });
  9. MyLinkComponentSpec.js //set up of mock props var props = {};

    beforeEach(function(){ props = { url: 'http://www.addthis.com/', isAdmin: true, text: 'MyLink' }; }); it('should default state', function(){ var container = ReactTestUtils.renderIntoDocument(MyLinkComponent(props)); expect(container.state.hasBeenClicked).toBe(false); }); it('should display link if user isAdmin', function(){ var container = ReactTestUtils.renderIntoDocument(MyLinkComponent(props)), myTag = ReactTestUtils.scryRenderedDOMComponentsWithTag(container,'a'); expect(myTag.length).toBe(1); expect(myTag[0].getDOMNode().href).toEqual(props.url); });
  10. it('should not display link if user is not Admin', function(){

    props.isAdmin = false; var container = ReactTestUtils.renderIntoDocument(MyLinkComponent(props)), //traverse components in container and return components where function eval is true myLinks = React.addons.TestUtils. findAllInRenderedTree(container, function(component){ return component.getDOMNode() && component.getDOMNode().text === props.text; }); expect(myLinks.length).toBe(0); }); it('should display link with class title-options', function(){ var container = ReactTestUtils.renderIntoDocument(MyLinkComponent(props)), //scry returns all instances, use find for only one instance. myDiv = ReactTestUtils.scryRenderedDOMComponentsWithClass(container,'title-options'); expect(myDiv.length).toBe(1); }); it('should change state when link is clicked', function(){ var container = ReactTestUtils.renderIntoDocument(MyLinkComponent(props)); ReactTestUtils.Simulate.click( ReactTestUtils.findRenderedDOMComponentWithTag(container, 'a')); expect(container.state.hasBeenClicked).toBe(true); }); MyLinkComponentSpec.js
  11. UserStore.js //AMD stuff omitted for brevity var CHANGE_EVENT = 'change';

    var _user; function _setUser(user){ _user = user; } var UserStore = merge(EventEmitter.prototype, { emitChange: function() { this.emit(CHANGE_EVENT); }, addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener: function(callback) { this.removeListener(CHANGE_EVENT, callback); }, getUsername: function() { return _user ? _user.username : false; }, isAdmin: function(){ return _user ? _user.isAdmin : false; } });
  12. UserStore.js UserStore.dispatchToken = AppDispatcher.register(function(payload) { var action = payload.action; switch(action.type)

    { // Empty out data when new user is set case ActionTypes.RESET_USER: _setUser(false); UserStore.emitChange(); break; case ActionTypes.RECEIVE_USER: _setUser(action.user); UserStore.emitChange(); break; default: // do nothing } }); return UserStore; });
  13. UserStoreSpec.js return describe('UserStore Component Tests', function(){ beforeEach(function(){ AppDispatcher.handleServerAction({ type: ActionTypes.RESET_USER

    }); }); it('should start with an empty user', function(){ expect(UserStore.getUsername()).toEqual(false); }); it('should set the user when received', function(){ expect(UserStore.getUsername()).toEqual(false); AppDispatcher.handleServerAction({ type: ActionTypes.RECEIVE_USER, user: { username: 'myUser', isAdmin: true } }); expect(UserStore.getUsername()).toEqual('myUser'); expect(UserStore.isAdmin()).toBe(true); }); });