Testing Flux Applications

September 24, 2014 by Bill Fisher


A more up-to-date version of this post is available as part of the Flux documentation.

Flux is the application architecture that Facebook uses to build web applications with React. It's based on a unidirectional data flow. In previous blog posts and documentation articles, we've shown the basic structure and data flow, more closely examined the dispatcher and action creators, and shown how to put it all together with a tutorial. Now let's look at how to do formal unit testing of Flux applications with Jest, Facebook's auto-mocking testing framework.

Testing with Jest #

For a unit test to operate on a truly isolated unit of the application, we need to mock every module except the one we are testing. Jest makes the mocking of other parts of a Flux application trivial. To illustrate testing with Jest, we'll return to our example TodoMVC application.

The first steps toward working with Jest are as follows:

  1. Get the module dependencies for the application installed by running npm install.
  2. Create a directory __tests__/ with a test file, in this case TodoStore-test.js
  3. Run npm install jest-cli —save-dev
  4. Add the following to your package.json
{
  ...
  "scripts": {
    "test": "jest"
  }
  ...
}

Now you're ready to run your tests from the command line with npm test.

By default, all modules are mocked, so the only boilerplate we need in TodoStore-test.js is a declarative call to Jest's dontMock() method.

jest.dontMock('TodoStore');

This tells Jest to let TodoStore be a real object with real, live methods. Jest will mock all other objects involved with the test.

Testing Stores #

At Facebook, Flux stores often receive a great deal of formal unit test coverage, as this is where the application state and logic lives. Stores are arguably the most important place in a Flux application to provide coverage, but at first glance, it's not entirely obvious how to test them.

By design, stores can't be modified from the outside. They have no setters. The only way new data can enter a store is through the callback it registers with the dispatcher.

We therefore need to simulate the Flux data flow with this one weird trick.

var mockRegister = MyDispatcher.register;
var mockRegisterInfo = mockRegister.mock;
var callsToRegister = mockRegisterInfo.calls;
var firstCall = callsToRegister[0];
var firstArgument = firstCall[0];
var callback = firstArgument;

We now have the store's registered callback, the sole mechanism by which data can enter the store.

For folks new to Jest, or mocks in general, it might not be entirely obvious what is happening in that code block, so let's look at each part of it a bit more closely. We start out by looking at the register() method of our application's dispatcher — the method that the store uses to register its callback with the dispatcher. The dispatcher has been thoroughly mocked automatically by Jest, so we can get a reference to the mocked version of the register() method just as we would normally refer to that method in our production code. But we can get additional information about that method with the mock property of that method. We don't often think of methods having properties, but in Jest, this idea is vital. Every method of a mocked object has this property, and it allows us to examine how the method is being called during the test. A chronologically ordered list of calls to register() is available with the calls property of mock, and each of these calls has a list of the arguments that were used in each method call.

So in this code, we are really saying, "Give me a reference to the first argument of the first call to MyDispatcher's register() method." That first argument is the store's callback, so now we have all we need to start testing. But first, we can save ourselves some semicolons and roll all of this into a single line:

callback = MyDispatcher.register.mock.calls[0][0];

We can invoke that callback whenever we like, independent of our application's dispatcher or action creators. We will, in fact, fake the behavior of the dispatcher and action creators by invoking the callback with an action that we'll create directly in our test.

var payload = {
  source: 'VIEW_ACTION',
  action: {
    actionType: TodoConstants.TODO_CREATE,
    text: 'foo'
  }
};
callback(payload);
var all = TodoStore.getAll();
var keys = Object.keys(all);
expect(all[keys[0]].text).toEqual('foo');

Putting it All Together #

The example Flux TodoMVC application has been updated with an example test for the TodoStore, but let's look at an abbreviated version of the entire test. The most important things to notice in this test are how we keep a reference to the store's registered callback in the closure of the test, and how we recreate the store before every test so that we clear the state of the store entirely.

jest.dontMock('../TodoStore');
jest.dontMock('react/lib/merge');

describe('TodoStore', function() {

  var TodoConstants = require('../../constants/TodoConstants');

  // mock actions inside dispatch payloads
  var actionTodoCreate = {
    source: 'VIEW_ACTION',
    action: {
      actionType: TodoConstants.TODO_CREATE,
      text: 'foo'
    }
  };
  var actionTodoDestroy = {
    source: 'VIEW_ACTION',
    action: {
      actionType: TodoConstants.TODO_DESTROY,
      id: 'replace me in test'
    }
  };

  var AppDispatcher;
  var TodoStore;
  var callback;

  beforeEach(function() {
    AppDispatcher = require('../../dispatcher/AppDispatcher');
    TodoStore = require('../TodoStore');
    callback = AppDispatcher.register.mock.calls[0][0];
  });

  it('registers a callback with the dispatcher', function() {
    expect(AppDispatcher.register.mock.calls.length).toBe(1);
  });

  it('initializes with no to-do items', function() {
    var all = TodoStore.getAll();
    expect(all).toEqual({});
  });

  it('creates a to-do item', function() {
    callback(actionTodoCreate);
    var all = TodoStore.getAll();
    var keys = Object.keys(all);
    expect(keys.length).toBe(1);
    expect(all[keys[0]].text).toEqual('foo');
  });

  it('destroys a to-do item', function() {
    callback(actionTodoCreate);
    var all = TodoStore.getAll();
    var keys = Object.keys(all);
    expect(keys.length).toBe(1);
    actionTodoDestroy.action.id = keys[0];
    callback(actionTodoDestroy);
    expect(all[keys[0]]).toBeUndefined();
  });

});

You can take a look at all this code in the TodoStore's tests on GitHub as well.

Mocking Data Derived from Other Stores #

Sometimes our stores rely on data from other stores. Because all of our modules are mocked, we'll need to simulate the data that comes from the other store. We can do this by retrieving the mock function and adding a custom return value to it.

var MyOtherStore = require('../MyOtherStore');
MyOtherStore.getState.mockReturnValue({
  '123': {
    id: '123',
    text: 'foo'
  },
  '456': {
    id: '456',
    text: 'bar'
  }
});

Now we have a collection of objects that will come back from MyOtherStore whenever we call MyOtherStore.getState() in our tests. Any application state can be simulated with a combination of these custom return values and the previously shown technique of working with the store's registered callback.

A brief example of this technique is up on GitHub within the Flux Chat example's UnreadThreadStore-test.js.

For more information about the mock property of mocked methods or Jest's ability to provide custom mock values, see Jest's documentation on mock functions.

Moving Logic from React to Stores #

What often starts as a little piece of seemingly benign logic in our React components often presents a problem while creating unit tests. We want to be able to write tests that read like a specification for our application's behavior, and when application logic slips into our view layer, this becomes more difficult.

For example, when a user has marked each of their to-do items as complete, the TodoMVC specification dictates that we should also change the status of the "Mark all as complete" checkbox automatically. To create that logic, we might be tempted to write code like this in our MainSection's render() method:

var allTodos = this.props.allTodos;
var allChecked = true;
for (var id in allTodos) {
  if (!allTodos[id].complete) {
    allChecked = false;
    break;
  }
}
...

return (
  <section id="main">
  <input
    id="toggle-all"
    type="checkbox"
    checked={allChecked ? 'checked' : ''}
  />
  ...
  </section>
);

While this seems like an easy, normal thing to do, this is an example of application logic slipping into the views, and it can't be described in our spec-style TodoStore test. Let's take that logic and move it to the store. First, we'll create a public method on the store that will encapsulate that logic:

areAllComplete: function() {
  for (var id in _todos) {
    if (!_todos[id].complete) {
      return false;
    }
  }
  return true;
},

Now we have the application logic where it belongs, and we can write the following test:

it('determines whether all to-do items are complete', function() {
  var i = 0;
  for (; i < 3; i++) {
    callback(mockTodoCreate);
  }
  expect(TodoStore.areAllComplete()).toBe(false);

  var all = TodoStore.getAll();
  for (key in all) {
    callback({
      source: 'VIEW_ACTION',
      action: {
        actionType: TodoConstants.TODO_COMPLETE,
        id: key
      }
    });
  }
  expect(TodoStore.areAllComplete()).toBe(true);

  callback({
    source: 'VIEW_ACTION',
    action: {
      actionType: TodoConstants.TODO_UNDO_COMPLETE,
      id: key
    }
  });
  expect(TodoStore.areAllComplete()).toBe(false);
});

Finally, we revise our view layer. We'll call for that data in the controller-view, TodoApp.js, and pass it down to the MainSection component.

function getTodoState() {
  return {
    allTodos: TodoStore.getAll(),
    areAllComplete: TodoStore.areAllComplete()
  };
}

var TodoApp = React.createClass({
...

  /**
   * @return {object}
   */
  render: function() {
    return (
      ...
      <MainSection
        allTodos={this.state.allTodos}
        areAllComplete={this.state.areAllComplete}
      />
      ...
    );
  },

  /**
   * Event handler for 'change' events coming from the TodoStore
   */
  _onChange: function() {
    this.setState(getTodoState());
  }

});

And then we'll utilize that property for the rendering of the checkbox.

render: function() {
  ...

  return (
    <section id="main">
    <input
      id="toggle-all"
      type="checkbox"
      checked={this.props.areAllComplete ? 'checked' : ''}
    />
    ...
    </section>
  );
},

To learn how to test React components themselves, check out the Jest tutorial for React and the ReactTestUtils documentation.

Further Reading #

React v0.11.2

September 16, 2014 by Paul O’Shannessy


Today we're releasing React v0.11.2 to add a few small features.

We're adding support for two more DOM elements, <dialog> and <picture>, as well as the associated attributes needed to use these elements: open, media, and sizes. While not all browsers support these natively, some of our cutting edge users want to make use of them, so we're making them available to everybody.

We're also doing some work to prepare for v0.12 and improve compatibility between the versions. To do this we are replacing React.createDescriptor with React.createElement. createDescriptor will continue to work with a warning and will be gone in v0.12. Chances are that this won't affect anybody.

And lastly, on the heels of announcing Flow at @Scale yesterday, we're adding the ability to strip TypeScript-like type annotations as part of the jsx transform. To use, simply use the --strip-types flag on the command line, or set stripTypes in the options object when calling the API. We'll be talking about Flow more in the coming months. But for now, it's helpful to know that it is a flow-sensitive JavaScript type checker we will be open sourcing soon.

The release is available for download from the CDN:

We've also published version 0.11.2 of the react and react-tools packages on npm and the react package on bower.

Please try these builds out and file an issue on GitHub if you see anything awry.

React Core #

New Features #

  • Added support for <dialog> element and associated open attribute
  • Added support for <picture> element and associated media and sizes attributes
  • Added React.createElement API in preparation for React v0.12
    • React.createDescriptor has been deprecated as a result

JSX #

  • <picture> is now parsed into React.DOM.picture

React Tools #

  • Update esprima and jstransform for correctness fixes
  • The jsx executable now exposes a --strip-types flag which can be used to remove TypeScript-like type annotations
    • This option is also exposed to require('react-tools').transform as stripTypes

Community Round-up #22

September 12, 2014 by Lou Husson


This has been an exciting summer as four big companies: Yahoo, Mozilla, Airbnb and Reddit announced that they were using React!

React's Architecture #

Vjeux, from the React team, gave a talk at OSCON on the history of React and the various optimizations strategies that are implemented. You can also check out the annotated slides or Chris Dawson's notes titled JavaScript’s History and How it Led To React.

v8 optimizations #

Jakob Kummerow landed two optimizations to V8 specifically targeted at optimizing React. That's really exciting to see browser vendors helping out on performance!

Reusable Components by Khan Academy #

Khan Academy released many high quality standalone components they are using. This is a good opportunity to see what React code used in production look like.

var TeX = require('react-components/js/tex.jsx');
React.renderComponent(<TeX>\nabla \cdot E = 4 \pi \rho</TeX>, domNode);

var translated = (
  <$_ first="Motoko" last="Kusanagi">
    Hello, %(first)s %(last)s!
  </$_>
);

React + Browserify + Gulp #

Trường wrote a little guide to help your getting started using React, Browserify and Gulp.

React Style #

After React put HTML inside of JavaScript, Sander Spies takes the same approach with CSS: IntegratedCSS. It seems weird at first but this is the direction where React is heading.

var Button = React.createClass({
  normalStyle: ReactStyle(function() {
    return { backgroundColor: vars.orange };
  }),
  activeStyle: ReactStyle(function() {
    if (this.state.active) {
      return { color: 'yellow', padding: '10px' };
    }
  }),
  render: function() {
    return (
      <div styles={[this.normalStyle(), this.activeStyle()]}>
        Hello, I'm styled
      </div>
    );
  }
});

Virtual DOM in Elm #

Evan Czaplicki explains how Elm implements the idea of a Virtual DOM and a diffing algorithm. This is great to see React ideas spread to other languages.

Performance is a good hook, but the real benefit is that this approach leads to code that is easier to understand and maintain. In short, it becomes very simple to create reusable HTML widgets and abstract out common patterns. This is why people with larger code bases should be interested in virtual DOM approaches.

Read the full article

Components Tutorial #

If you are getting started with React, Joe Maddalone made a good tutorial on how to build your first component.

Saving time & staying sane? #

When Kent William Innholt who works at M>Path summed up his experience using React in an article.

We're building an ambitious new web app, where the UI complexity represents most of the app's complexity overall. It includes a tremendous amount of UI widgets as well as a lot rules on what-to-show-when. This is exactly the sort of situation React.js was built to simplify.

  • Big win: Tighter coupling of markup and behavior
  • Jury's still out: CSS lives outside React.js
  • Big win: Cascading updates and functional thinking
  • Jury's still out: Verbose propagation

Read the article...

Weather #

To finish this round-up, Andrew Gleave made a page that displays the Weather. It's great to see that React is also used for small prototypes.

Introducing the JSX Specification

September 3, 2014 by Sebastian Markbåge


At Facebook we've been using JSX for a long time. We originally introduced it to the world last year alongside React, but we actually used it in another form before that to create native DOM nodes. We've also seen some similar efforts grow out of our work in order to be used with other libraries and in interesting ways. At this point React JSX is just one of many implementations.

In order to make it easier to implement new versions and to make sure that the syntax remains compatible, we're now formalizing the syntax of JSX in a stand-alone spec without any semantic meaning. It's completely stand-alone from React itself.

Read the spec now at https://facebook.github.io/jsx/.

This is not a proposal to be standardized in ECMAScript. It's just a reference document that transpiler writers and syntax highlighters can agree on. It's currently in a draft stage and will probably continue to be a living document.

Feel free to open an Issue or Pull Request if you find something wrong.

Community Round-up #21

August 3, 2014 by Lou Husson


React Router #

Ryan Florence and Michael Jackson ported Ember's router to React in a project called React Router. This is a very good example of both communities working together to make the web better!

React.renderComponent((
  <Routes>
    <Route handler={App}>
      <Route name="about" handler={About}/>
      <Route name="users" handler={Users}>
        <Route name="user" path="/user/:userId" handler={User}/>
      </Route>
    </Route>
  </Routes>
), document.getElementById('example'));

Going Big with React #

Areeb Malik, from Facebook, talks about his experience using React. "On paper, all those JS frameworks look promising: clean implementations, quick code design, flawless execution. But what happens when you stress test Javascript? What happens when you throw 6 megabytes of code at it? In this talk, we'll investigate how React performs in a high stress situation, and how it has helped our team build safe code on a massive scale"

What is React? #

Craig McKeachie author of Javascript Framework Guide wrote an excellent news named "What is React.js? Another Template Library?

  • Is React a template library?
  • Is React similar to Web Components?
  • Are the Virtual DOM and Shadow DOM the same?
  • Can React be used with other JavaScript MVC frameworks?
  • Who uses React?
  • Is React a premature optimization if you aren’t Facebook or Instagram?
  • Can I work with designers?
  • Will React hurt my search engine optimizations (SEO)?
  • Is React a framework for building applications or a library with one feature?
  • Are components a better way to build an application?
  • Can I build something complex with React?

Referencing Dynamic Children #

While Matt Zabriskie was working on react-tabs he discovered how to use React.Children.map and React.addons.cloneWithProps in order to reference dynamic children.

var App = React.createClass({
  render: function () {
    var children = React.Children.map(this.props.children, function(child, index) {
      return React.addons.cloneWithProps(child, {
        ref: 'child-' + index
      });
    });
    return <div>{children}</div>;
  }
});

JSX with Sweet.js using Readtables #

Have you ever wondered how JSX was implemented? James Long wrote a very instructive blog post that explains how to compile JSX with Sweet.js using Readtables.

First Look: Getting Started with React #

Kirill Buga wrote an article on Modern Web explaining how React is different from traditional MVC used by most JavaScript applications

React Draggable #

Matt Zabriskie released a project to make your react components draggable.

HTML Parser2 React #

Jason Brown adapted htmlparser2 to React: htmlparser2-react. That allows you to convert raw HTML to the virtual DOM. This is not the intended way to use React but can be useful as last resort if you have an existing piece of HTML.

var html = '<div data-id="1" class="hey this is a class" ' +
  'style="width:100%;height: 100%;"><article id="this-article">' +
  '<p>hey this is a paragraph</p><div><ul><li>1</li><li>2</li>' +
  '<li>3</li></ul></div></article></div>';
var parsedComponent = reactParser(html, React);

Building UIs with React #

If you haven't yet tried out React, Jacob Rios did a Hangout where he covers the most important aspects and thankfully he recorded it!

Random Tweets #