Aaron Klaser

@aaron.klaser

Testing Redux Sagas

As much as want to take credit for this post this was mostly written by John Pipkin. It was real good and helped me a lot and I wanted it to be shared with the world.

Click here for his full article

So testing generators it’s best to think of them as loops that execute when you tell them to.

Step 1

it(‘should return 6’, () => {
// we’ve set up the generator, but we haven’t called next yet so we’re not at a yield
const gen = count()
expect(gen.next().value).toEqual(call(addNumber, number, 1))
expect(gen.next(1).value).toEqual(call(addNumber, number, 2))
expect(gen.next(3).value).toEqual(call(addNumber, number, 3))
expect(gen.next(6).value).toEqual(put(something(6)))
})
function *count() {
-> // We haven’t told the generator to exicute anything yet so we’re not at a yield
let number = 0
number = yield call(addNumber, number, 1)
number = yield call(addNumber, number, 2) //3
number = yield call(addNumber, number, 3) //6
yield.put(something(number))
}

In this step we haven’t told the generator to do anything yet, so we don’t have anything to test.

Step 2

it(‘should return 6’, () => {
const gen = count()
// This next is called, we’re seeing what the yield will be
expect(gen.next().value).toEqual(call(addNumber, number, 1))
expect(gen.next(1).value).toEqual(call(addNumber, number,2))
expect(gen.next(3).value).toEqual(call(addNumber, number, 3))
expect(gen.next(6).value).toEqual(put(something(6)))
})
function *count() {
let number = 0
// Generator is paused here, waiting. The yield call(addNumber, number, 1) hasn’t executed
-> number = yield call(addNumber, number, 1)
number = yield call(addNumber, number, 2) //3
number = yield call(addNumber, number, 3) //6
yield.put(something(number))
}

Here we’ve executed gen.next(). We’ve told the generator to go the yield statement and wait. So now we’re paused at the ->, but we haven’t actually ran the code.

When we get gen.next().value here we’re just getting what the yield WILL do.

Step 3

it(‘should return 6’, () => {
const gen = count()
expect(gen.next().value).toEqual(call(addNumber, number, 1))
// called next here, this evaluated the previous line. We pass in the return value from the last line
expect(gen.next(1).value).toEqual(call(addNumber, number, 2))
expect(gen.next(3).value).toEqual(call(addNumber, number, 3))
expect(gen.next(6).value).toEqual(put(something(6)))
})
function *count() {
let number = 0
number = yield call(addNumber, number, 1)
// We evaluated the previous line, now were waiting at the this yield
-> number = yield call(addNumber, number, 2)
number = yield call(addNumber, number, 3) //6
yield.put(something(number))
}

Here we’ve called gen.next() again. The generator sees that command and executes the yield that it was paused at and continues the next `yield` statement. It will pause here ->. gen.next() tells the yield statement it was at to execute, we can pass what we want returned from that into the call
We were paused at yield call(addNumber, number, 1), we wanted that to return 1 so we pass that in when we actually execute that line.

Step 4

it(‘should return 6’, () => {
const gen = count()
expect(gen.next().value).toEqual(call(addNumber, number,1))
expect(gen.next(1).value).toEqual(call(addNumber, number, 2))
// Called next again, we evaluated the call to (number, 2) and specifiy its return value in this next
expect(gen.next(3).value).toEqual(call(addNumber, number, 3))
expect(gen.next(6).value).toEqual(put(something(6)))
})
function *count() {
let number = 0
number = yield call(addNumber, number, 1)
number = yield call(addNumber, number, 2) //3
-> number = yield call(addNumber, number,3) //6
yield.put(something(number))
}

This one is just like the previous step, just to illustrate what I’m talking about. The yield to call(addNumber, number, 2) should return 3 so we pass that in to the call to gen.next(3) because we’ve told the generator to execute that line and we want it to return 3.

Step 5

it(‘should return 6’, () => {
const gen = count()
expect(gen.next().value).toEqual(call(addNumber, number, 1))
expect(gen.next(1).value).toEqual(call(addNumber, number, 2))
expect(gen.next(3).value).toEqual(call(addNumber, number, 3))
// Called next again, we evaluated the call to (number, 3) and specifiy its return value in this next.
// I’ve intentionally returned the wrong value from the call to (addNumber, number, 3) to illustrate how the return in next works
expect(gen.next(8).value).toEqual(put(something(8)))
})
function *count() {
let number = 0
number = yield call(addNumber, number, 1)
number = yield call(addNumber, number, 2) //3
number = yield call(addNumber, number, 3) //6
-> yield.put(something(number))
}

We’ve called gen.next() again so now we’re waiting at the yield to put. In this example you can see I intentionally returned the wrong value `8` in gen.next(). This is to illustrate that next takes in the return value of the previous execution. Essentailly mock data in this case. Now we see that the yield we’re paused at is the put(number). Since number in this case is 8 (because that’s what we told call(addNumber, number, 3) to return, that’s what we’re looking for.

Step 6

it(‘should return 6’, () => {
const gen = count()
expect(gen.next().value).toEqual(call(addNumber, number, 1))
expect(gen.next(1).value).toEqual(call(addNumber, number, 2))
expect(gen.next(3).value).toEqual(call(addNumber, number, 3))
expect(gen.next(8).value).toEqual(put(something(8)))
// Finally call next again to see there are no more yields
expect(gen.next().done).toBeTruthy()
})
function *count() {
let number = 0
number = yield call(addNumber, number, 1)
number = yield call(addNumber, number, 2) //3
number = yield call(addNumber, number, 3) //6
yield.put(something(number))
->
}

Finally, we call gen.next() again. Since there are no more yields to wait for we can see the saga is done.

More by Aaron Klaser

Topics of interest

More Related Stories