向商店调度动作
进一步考虑之前的示例,假设在每次保存后,我们希望调度一些动作来通知商店获取已成功(我们暂时省略失败情况)。
我们可以将商店的 dispatch
函数传递给生成器。然后生成器可以在收到获取响应后调用它。
// ...
function* fetchProducts(dispatch) {
const products = yield call(Api.fetch, '/products')
dispatch({ type: 'PRODUCTS_RECEIVED', products })
}
但是,此解决方案与从生成器内部直接调用函数具有相同的缺点(如上一节所述)。如果我们想测试 fetchProducts
在收到 AJAX 响应后执行调度,我们将再次需要模拟 dispatch
函数。
相反,我们需要相同的声明式解决方案。创建一个纯 JavaScript 对象来指示中间件我们需要调度一些动作,并让中间件执行实际的调度。这样,我们可以以相同的方式测试生成器的调度:通过检查生成的 Effect 并确保它包含正确的指令。
为了这个目的,库提供了另一个函数 put
,它创建调度 Effect。
import { call, put } from 'redux-saga/effects'
// ...
function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
// create and yield a dispatch Effect
yield put({ type: 'PRODUCTS_RECEIVED', products })
}
现在,我们可以像上一节一样轻松地测试 Generator。
import { call, put } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
// expects a call instruction
assert.deepEqual(
iterator.next().value,
call(Api.fetch, '/products'),
"fetchProducts should yield an Effect call(Api.fetch, './products')"
)
// create a fake response
const products = {}
// expects a dispatch instruction
assert.deepEqual(
iterator.next(products).value,
put({ type: 'PRODUCTS_RECEIVED', products }),
"fetchProducts should yield an Effect put({ type: 'PRODUCTS_RECEIVED', products })"
)
请注意,我们如何通过 Generator 的 next
方法将假响应传递给它。在中间件环境之外,我们完全控制 Generator,可以通过模拟结果并用它们恢复 Generator 来模拟真实环境。模拟数据比模拟函数和间谍调用要容易得多。