TodoList example

In the next example we’ll try to implement a simple TodoList in a TDD fashion — what else 😄?

We start with simply passing our not-yet-existing TodoList component to reduxTdd:

reduxTdd({}, state => [ <TodoList /> ])

Our list items will probably live in state, so let’s go ahead next and introduce a list reducer, and pass that along to our component as a prop called “listItems”.

reduxTdd({ list }, state => [ <TodoList listItems={state.list} /> ])

Running this will of course fail, since nothing is yet implemented, but let’s write more tests before implementing our logic.

A todo-list is meant to have an input-box where you add todos so let’s also add an AddTodo component (remember we’re returning an array so we can test multiple components):

reduxTdd({ list }, state => [

<TodoList listItems={state.list} />,

<AddTodo onAdd={addTodo} />

])

Great now let’s continue the dot-chaining from the above example and actually simulate the addition of a todo by calling the onAdd action creator:

.it('should add todo items')

.switch(AddTodo) // the next dot-chained calls will work on AddTodo

.action(props => props.onAdd('clean house')) // add 'clean house'

So next we should be seeing the “clean house” todo in the TodoList, right? Let’s test that:

.switch(TodoList) // back to TodoList

.toMatchProps({ listItems: ["clean house"] })

We can even add another todo:

.switch(AddTodo).action(props => props.onAdd('water plants'))

.switch(TodoList).toMatchProps({ listItems: [

"clean house",

"water plants"

]})

Great that’s enough for now, let’s run this code and start implementing our components, reducers and actions:

const TodoList = ({ listItems }) =>

<div>{listItems.map(item => <div>{item}</div>)}</div> const AddTodo = ({ onAdd }) =>

<button onClick={onAdd}>add todo</button>

(hint: the text-input logic is purposely left out of AddTodo)

Our addTodo action:

function addTodo(todoText) {

return { type: 'ADD_TODO', payload: todoText }

}

And finally our reducer:

function list(state = {}, action) {

switch (action.type) {

case 'ADD_TODO':

return { ...state, [action.payload]: {} }

default:

return state

}

}

If we run our code again some things will fail:

TypeError: listItems.map is not a function

This is because our state.list is an object, but the listItems prop expects an array. Let’s fix that using a selector (you can even use reselect if you want) ❤️

In our initial reduxTdd definition let’s change the way state maps to props for our TodoList component:

reduxTdd({ list }, state => [

<TodoList listItems={getVisibleItems(state)} />,

And the selector:

function getVisibleItems(state) {

return Object.keys(state.list).length

? Object.keys(state.list).map(key => key)

: []

}

Now if we run our tests again we get:

TodoList

✓ should add todo items (1ms)

🎉

Let’s also TDD the idea of “setting a todo item as complete”.

For this I’m thinking there should be a TodoItem component that is clickable right? Let’s add it to our initial list:

reduxTdd({ list }, state => [

<TodoList listItems={getVisibleItems(state)} />,

<AddTodo onAdd={addTodo} />,

<TodoItem onClick={completeTodo} />

])

But there’s a problem! The TodoItem must know which item it must complete. And it must work with a single item, but in our state we keep an object! To solve this we can simply pass a single item from our state.list object:

reduxTdd({ list }, state => [

<TodoList listItems={getVisibleItems(state)} />,

<AddTodo onAdd={addTodo} />,

<TodoItem onClick={completeTodo} item={state.list['water plants']}

/>

])

Next we mark it as complete:

.switch(TodoItem)

.action(props => props.onClick('water plants'))

At this point we realize that our TodoList component has no way of showing whether the item is complete or not (there should be a striked line over it). So we pass a new “completedItems” prop which is an array of the items that are complete:

.switch(TodoList)

.toMatchProps({

listItems: ["clean house", "water plants"]

completedItems: ["water plants"]

})

And we change again our definition to work for such behavior:

reduxTdd({ list }, state => [

<TodoList

listItems={getVisibleItems(state)}

completedItems={getCompletedItems(state)} />,

...

If we run our tests, they tell us some things need to be implemented like our completeTodo action creator:

function completeTodo(todoText) {

return { type: 'COMPLETE_TODO', payload: todoText }

}

And we update our reducer accordingly:

function list(state = {}, action) {

switch (action.type) {

case 'ADD_TODO':

return { ...state, [action.payload]: {} }

case 'COMPLETE_TODO':

return { ...state, [action.payload]: { status: 'complete' } }

default:

return state

}

}

Again running our tests will tell us our getCompletedItems is missing:

function getCompletedItems(state) {

return Object.keys(state.list)

.map(item => state.list[item])

.filter(item => item.status === 'complete')

}

And we’re done!

We can go further and use other operators like contains and view to test more fine-grained parts of our UI. But the main parts of our app have been implemented step-by-step in a TDD way.

More importantly we didn’t have to come up with intricate engineering decisions about our state or our props beforehand. We implemented them as we went ahead and as we saw the need.

I hope these examples helped you understand the idea behind Redux TDD and how you can use it for your own projects. To look at more complex examples please have a look at the tests/ folder inside the repo. You’ll find another important operator called epic which can be used to test async behavior!

Thanks and happy TDD 🤗