DEV Community

Cathy Casey-Richards
Cathy Casey-Richards

Posted on

Testing Redux Selectors with resultFunc

Over the years, as I've been using Redux, I've tried quite a few different forms of testing. Actions, reducers, sagas, selectors, some with more boiler plate results than others. One of the evolutions of testing that I've enjoyed the most has been with the library I use for selectors, reselect.

When I initially started testing selectors, I was mocking out entire state trees. This is pretty simple when your selector is interacting with a tiny subset of state, but can be much more difficult as your state tree grows.

In this example, I have a simple selector I'm using to turn an array of strings into an array of objects with a value & label.

import { createSelector } from 'reselect'
import map from 'lodash/map'
import { selectToDoListItems } from '.'

const selectFormattedToDoListItems = createSelector(
  selectToDoListItems, // state.todo.items
  items => map(items, item => ({
    label: item.toUpperCase(),
    value: item,
  })),
)

export default selectFormattedToDoListItems
Enter fullscreen mode Exit fullscreen mode

So how can I go about testing this?

Option 1 -- Testing via state tree

Since I know that the selector selectToDoListItems is pulling from state.todo.items, I can mock out what my relevant state tree looks like, and pass this into the function.

const state = {
      todo: {
        items: [
          'write blog post',
          'walk dog',
          'take out trash',
        ],
      },
    }
Enter fullscreen mode Exit fullscreen mode

We then write out our expected result from this selector and test it. The entire block looks something like this:

import selectFormattedToDoListItems from '../selectFormattedToDoListItems'

describe('selectFormattedToDoListItems', () => {
  it('returns the items from a todo list', () => {
    const state = {
      todo: {
        items: [
          'write blog post',
          'walk dog',
          'take out trash',
        ],
      },
    }
    const expectedOutput = [
      {
        label: 'WRITE BLOG POST',
        value: 'write blog post',
      },
      {
        label: 'WALK DOG',
        value: 'walk dog',
      },
      {
        label: 'TAKE OUT TRASH',
        value: 'take out trash',
      },
    ]

    expect(selectFormattedToDoListItems(state)).toEqual(expectedOutput)
  })
})
Enter fullscreen mode Exit fullscreen mode

This can be a simple solution for a small state tree, but if your state tree is large or your selector is utilizing many other complex selectors, managing a mock version of your state tree can be frustrating and brittle.

An Alternative -- resultFunc

The pattern I have gravitated towards over the last couple of years is using reselect's built in testing option resultFunc. Utilizing this function, we can pass in exactly the data format we are looking for. No matter if we are using a selector that has manipulated our state tree, we can simply mock out exactly what the inputs of our selector will look like. Using our above test as an example, updating it to use resultFunc looks as follows:

it('returns the items from a todo list using resultFunc', () => {
    const items = [
      'write blog post',
      'walk dog',
      'take out trash',
    ]
    const expectedOutput = [
      {
        label: 'WRITE BLOG POST',
        value: 'write blog post',
      },
      {
        label: 'WALK DOG',
        value: 'walk dog',
      },
      {
        label: 'TAKE OUT TRASH',
        value: 'take out trash',
      },
    ]

    expect(selectFormattedToDoListItems.resultFunc(items)).toEqual(expectedOutput)
  })
Enter fullscreen mode Exit fullscreen mode

Notice the change in the parameter of the expect statement. This cuts down on our need to mock out the state tree exactly as designed, since we can instead pass in a replica of the relevant data.

The bottom line

Ultimately, these are both viable ways of testing selectors. Where I find resultFunc to be especially useful is in cases where the selector I am testing is utilizing other selectors that manipulate data in a way that would require the mock data I create to effectively reimplement the adjacent selectors. In future articles, I will demonstrate this via more expansive examples.

Thanks for reading out my article, and if you are inclined, check out my favorite Git client GitKraken! Use this link for a chance to win a $100 Amazon gift card :)

Top comments (0)