:rooster: An opinionated guide on how to test Redux ducks
This repo is a simple example project demonstrating how to test the different components of a Redux application.
The general idea: Don't write individual unit tests for your action creators, reducers, and selectors; instead, write tests that test the whole "duck".
This strategy comes from this Redux issue thread in which @bvaughn describes a way of testing Redux applications in order to get "the most bang for your buck" (dare I say "bang for your duck"?).
See the motivation section for more details on this testing strategy.
Examples of bad duck tests are found here.
Examples of good duck tests are found here.
Examples of good duck tests involving sagas are found here.
First of all, what is a duck? It's a combination of related action creators, a reducer, and selectors. (It can also include other Redux-related paraphernalia, such as sagas.) Action creators create actions that when dispatched, cause state changes in the reducer, which ultimately are manifested in different values returned by the selectors.
You can read more about ducks in the ducks-modular-redux project.
How do we test our ducks? The redux documentation gives examples of how to write unit tests for action creators, reducers, and selectors, i.e. how to test them each in isolation. So why don't we start by writing unit tests? That's what the testing pyramid suggests, after all:
There are some examples of these kind of unit tests in this project, in one of the duck test files.
Let's step back for a moment and recall the purpose of tests:
So good tests have the following properties:
With that in mind, consider what happens to our isolated action creator tests, reducer test, and selector tests when we do the following:
It turns out lots of bad things happen:
How do we fix these problems?
Write tests that test the whole duck! You can see examples of such tests in another duck test file. Notice how these tests are:
Think of a duck as having a public API: action creators and selectors are how you interact with the duck. They represent a "public interface" to the duck. If you only write tests using action creators and selectors, your tests are resilient to change as long as you maintain the same interface for your action creators and selectors.
You're not going to have an action if it doesn't trigger a state change, or at least have the potential to trigger some state change. You're not going to have a selector unless it gives you part of the state that gets changed by the dispatch of some action. So when it comes to ducks, don't think of isolated unit tests, think of the combined action/reducer/selector tests. The unit you want to be testing is the "three-part unit" of action creator, reducer, and selector. They are not distinct entities, they only have meaning when considered in relation to each other.
Set up:
yarn install
yarn start
Run tests:
yarn test
Thanks goes to these wonderful people (emoji key):
Michael Rose 💻 ⚠️ 📖 🚇 |
Christian Battista 📖 |
Noah 📖 |
This project follows the all-contributors specification. Contributions of any kind welcome!
Thanks to LogoMakr for having an impressive bounty of free stock duck images.