This post originally appeared on The Quick Left Blog
Introduction
A lot of people in the JavaScript community are pretty excited about Facebook’s React library, and associated Flux architecture. We’ve been using quite a bit of these tools in our client-side projects at Quick Left. It can be a little hard to wrap your mind around the way the data flows at first, but once you get used to it, you come to appreciate how clean it can be.
As with any development, test-driving features is the way to go in a Flux app. As I’ve been learning this technology, I’ve been collecting some of the less obvious patterns that make testing easier. In this post, we’ll take a look at some of these strategies, to make it easier for you to build the next big thing.
Setup
Project Structure
Since Flux is a more of an idea than a framework, there’s no convention as to how to structure your project. I personally like to break it down in a fairly obvious fashion, with the different Flux objects grouped together by folder.
1 2 3 4 5 6 7 8 |
|
There are a couple of places to put the tests, but I’ve been leaning toward a pattern where the tests live right alongside their corresponding files. This makes it easy to find the test for a given module. It also keeps the directory structures from getting out of sync, as they might if you put everything into a separate test/
folder. Here’s an example of what this might look like.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
When we find the files to run in our test suite, we can just use globbing to find them all, so it doesn’t really present any problems for setting up our tests.
Testing Dependencies
Facebook recommends using their testing tool, Jest, to test React and Flux components. Although I totally respect Jest, it doesn’t run in the browser, plus I’m pretty used to the toolchain I’m about to describe, so I go about things a slightly different way.
Mocha + Chai
When it comes to testing frameworks, I’m a big fan of Mocha. It gives us describe
and all of the other BDD-style assertions we could want when combined with ChaiJS.
Sinon
Since there are a lot of dependencies in a Flux app, we’ll probably be doing a lot of stubbing. I like to use SinonJS for this purpose. It gives us stubs and spies, and its API provides the ability to drill down into how functions were called and with what arguments with a level of granularity that can come in really useful.
Karma
When it comes to test runners, there are many viable choices. Lately, I’ve been leaning toward Karma for most of my needs, because it’s easy to get set up, and it can be hooked into a coverage tool with ease.
Here’s an example karma.conf
file for a Flux app in ES6 with Browserify and Babel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
Coverage
If you’re interesting in setting up a coverage tool to see how well-tested your code base is, check out my post Measuring Clientside JavaScript Test Coverage With Istanbul.
NPM Build Scripts
As far as running tasks, you can rely on Grunt or Gulp, or you can just set a test script up in your package.json
file. Doing it this way, running tests is as simple as typing npm test
. Here’s what to put into package.json
:
1 2 3 |
|
With these dependencies set up, you’re all ready to start writing tests. Let’s take a look at some of the testing specifics.
Actually Writing Tests
There are four parts to a Flux app: Actions
, Stores
, Views
, and Dispatchers
.
As mentioned above, Flux is more of a pattern than a framework. Although several people have released experimental frameworks built in its image, there is only one official Facebook package, called flux
. Ironically, it only contains a Dispatcher
. You can find the source code here. Since this package works well and is tested externally, so we won’t be looking at testing Dispatchers
in this post.
Before we get into looking at the remaining parts of Flux in depth, here are a couple of tips that come in handy in all cases.
Any Object
Setting Up A Sandbox
Sinon sandboxes are a great way to use stubs and spies without having to restore the objects they’re touching later. You can clean things up automatically by setting up a new sandbox before each test and tearing it down afterwards.
1 2 3 4 5 6 7 |
|
Getting Dependencies
Sometimes it can be a pain to pull in and/or stub a bunch of dependencies for an object you’re testing. There’s an easy way to grab what you need from within the test: using Rewire, which exposes a special __get__
method you can use to access whatever you need from the top level scope of the module. You can then stub out methods and properties on those modules. Here’s how to leverage it to your advantage.
1 2 3 4 5 6 7 8 9 10 11 |
|
As a note, you don’t even need to use Rewire unless you need access to instance variables on your objects. Since Flux uses plain objects, multiple calls to require
will always return the same object. This means that you can just spy on or stub out a method on one of your Actions
or Stores
directly after requiring them.
Actions
Testing Event Dispatching
When you’re writing a Flux action, it typically sends some kind of event and payload to the AppDispatcher
to trigger events registered elsewhere in the application. It’s easy to spy on the AppDispatcher
and test that it’s called with the right arguments to ensure that your Action
is working properly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Testing Promises
We often load data from a remote server in our Action
objects, so there are typically a lot of promises involved in its internals. When testing these methods, it’s often useful to stub out these promises. It’s pretty easy to do using native promises in ES6. Note that we use setTimeout
and done
to ensure that the promise is fully resolved before testing our assertion and moving on to the next test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Stores
In my own Flux projects, I have tried to keep the external API of stores
as “dumb” as possible. They are meant to be simple repositories for business objects that expose an interface for other objects to subscribe to change events. I typically define methods named emitChange
, addChangeListener
, and removeEventListener
for each store.
Despite their relatively simple API, it is vitally important to test your stores
. They’re usually the place where the business logic lives. Plus, they’re responsible for loading data from the server into the client-side app. For these reasons, we want to make sure they work properly. Here are a couple of tricks that can be helpful.
Using Internals
Given that stores
are only supposed to accept data through the callback they register with the dispatcher
, it can be tricky to send mocked data into them while testing. Facebook has one suggested way of doing it with Jest, or you can try this approach with Mocha or Jasmine. Alternatively, another nice way to hide the implementation a store uses to fetch its data is to wrap the fetch implementation in an internals
object and test that instead. Here’s what it looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
When it comes to testing this internals
object, we can test that the internals
methods are behaving as expected. For example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Using Dependency Injection
If you write your stores
in an object-oriented way, you can pass a reference to the dispatcher
directly into them. This makes it easier to test that different dispatching events trigger the correct callbacks to produce the behavior that is desired. Here’s an example (thanks to Jack Hsu for the inspiration for this tip).
1 2 3 4 5 6 7 8 9 10 11 12 |
|
And now testing the store is simple. We can just inject a dispatcher and use it to trigger the events we want to test.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
View Components
Finally we come to the view layer. If you’ve played with React, writing these view components should come naturally.
When it comes to testing, there are a lot of things you can safely skip, since testing them would just be verifying that React works as expected. For example, checking that onClick
handlers fire is pointless, since we know that React will call them. On the other hand, it can be useful that the behavior we want them to cause is actually carried out.
Wrap Components With StubRouterContext
It’s usually a good idea to wrap your components in a stubbed out context, to make it easier to force them to behave the way you want within your tests. If you don’t, it can be hard to get them to render and behave as expected.
To get this to happen, I recommend using the stub-router-context module from the react-router project. It’s useful for wrapping the context of all kinds of components aside from the React Router. Although I tend to stick to the name “Stub Router Context”, it would perhaps be more accurate to just call it “stub context”, since you can use it to stub out any context.
I also like to add a ref to the stub in the component that’s returned in render
, to make it easier to get hold of the component being wrapped by the component returned by stub-router-context
.
1 2 3 |
|
Here’s how it looks when you include it in your test. Note how the ref I included makes it easy to grab the child and call setState
on it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Use the React TestUtils
When it comes to rendering your component into a test DOM, checking whether classes are being dynamically added or removed, or whether input values are are changing in response to user interactions, the React TestUtils can’t be beat. Get to know the TestUtils API, and use it to test your view components. It makes things much less painful.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
Conclusion
Committing to a test-driven development approach in client-side JavaScript applications can sometimes be a hard sell. Aside from problems with testing DOM manipulation and asynchronous code, it can also be hard to test patterns that are new to the team, like Flux. But with the right tools, it can become second nature to test some of these things. Once your team has the confidence that they can effectively test these components, it’s a lot easier to approach all feature development with a TDD mindset.
In this post, we explored how to better test Flux applications. We took a look at some of the JS testing tools that can be helpful to get setup in your build process. We talked about some testing tips that are useful across all of the different Flux objects. Then we drilled down into testing tips specific to Actions
, Stores
, and View Components
.
I hope that some of these tips come in useful for your team as you build your cutting-edge web application. Best of luck!