Pre-requisites:
- Our
jest
unit and integration testing framework and helpers (link). - The
enzyme
package of React-specific testing utilities (link to enzyme 'getting started docs' or blog post)
I. Behavior-based Unit Tests - ShallowWrapper
is your friend.
A. Always use our helper setupShallowTest
(or the underlying shallow
method from enzyme
directly) unless you have a really, really good reason not to. Currently accepted 'really good reasons' include:
1. A ComponentClass
decorated with @withGraphQL
, @withPubSub
, or one of their variants. Since the HoC(s) abstract away the vast majority of the data-layer handling, this can still be considered a presentational component if it is essentially rendering data from the back-end(s) with no business logic other than a few notable exceptions. If your component requires more than two of the functionalities below, consider giving it a container to abstract away as much as possible but the actual markup template.
a. Loading-state markup, manipulating presentation helpers like Scroll
.
b. Error-state markup, error reporting, re-fetching on error / user input.
c. Reconciling data from multiple back-end services other than our graphql
endpoint (find out if it is accurate to call this visage
instead?)
d. another one.
2. That's it. Export your 'bare' presentational components before applying functional HoCs (ask @prencher if that's the name), and subject those to tests.
B. Test the Public API. ShallowWrapper
allows you to reach into the component and make assertions against its internal state, or even call setState
or lifecycle methods directly. Don't do this. It is probably not a meaningful test, for one. Instead:
1. Simulate the expected behavior of HoCs with mocked props. There are (helpers) for this.
2. Assert that the correct props are passed down to child components based on the initial state (including things like core-ui
components).
3. Simulate the expected behavior of child components by invoking the callbacks passed to them by the component under test with every combination of parameters they might actually receive that will create a change to the state of the component under test.
4. Assert that the props passed to children correlate properly to the internal state changes induced in each permutation of the previous step.
We are testing a stateful ComponentClass
that is tasked with receiving some video objects from a parent, filtering and sorting them, and mapping them to instances of a presentational VideoCard
. It also is tasked with handling input from one child - a filter selector - and updating the order of the VideoCard
s accordingly. We could start to write tests for it like this:
/** Standard setup omitted for brevity; more details can be found here (link) */
beforeEach(() => {
{ wrapper, props } = setupShallow();
})
describe('VideoList should', () => {
it('render the correct number of videocards in the order provided', () => {
const renderedVideoCardTitles = wrapper.find(VideoCard).map((card: ShallowWrapper) => card.prop('title'));
const mockedVideoCardTitles = mockVideos().map((video: Video) => video.title);
expect(renderedVideoCardTitles).toEqual(mockedVideoCardtitles);
});
it('handle filter selections by updating the VideoCard sort order', () => {
const mockedSortedVideoCardTitles = mockVideos({ sort: FilterSort.TimePosted, ascending: true })
.map((video: Video) => video.title);
wrapper.find({ data-test-selector: FILTER_MENU_SELECTOR }).prop('onFilterSelect')({ sort: FilterSort.TimePosted, ascending: true });
const mockedVideoCardTitles = mockVideos().map((video: Video) => video.title);
expect(renderedVideoCardTitles).toEqual(mockedVideoCardtitles);
});
});
II. Container tests, page tests, integration tests - time to mount
up.
A.
B.
C. Don't test the HoCs. They have their own tests. If you find that they don't, bring it up with Browser Clients
.