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 yieldconst 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 yieldlet number = 0
number = yield call(addNumber, number, 1)number = yield call(addNumber, number, 2) //3number = yield call(addNumber, number, 3) //6yield.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 beexpect(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) //3number = yield call(addNumber, number, 3) //6yield.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 lineexpect(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) //6yield.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 callWe 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 nextexpect(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) //6yield.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 worksexpect(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) //3number = 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 yieldsexpect(gen.next().done).toBeTruthy()})
function *count() {let number = 0
number = yield call(addNumber, number, 1)number = yield call(addNumber, number, 2) //3number = yield call(addNumber, number, 3) //6yield.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.