-
Notifications
You must be signed in to change notification settings - Fork 293
Sagas
ARc uses redux-saga
to perform side effects based on dispatch
ed actions.
Basically, you will see 3 types of sagas on sagas.js
files: root saga
, watcher sagas
and worker sagas
.
They are responsible for actually performing the side effects and, in response, dispatching another actions asynchronously. A simple worker saga will look like:
export function* updateResource(needle, data) {
try {
const detail = yield call(api.put, `/resources/${needle}`, data)
yield put(resourceUpdateSuccess(needle, detail)) // dispatches another action
} catch (e) {
yield put(resourceUpdateFailure(needle, e))
}
}
They are responsible for listening to dispatched actions and calling worker sagas in response to that:
export function* watchResourceUpdateRequest() {
while (true) {
const { needle, data } = yield take('RESOURCE_UPDATE_REQUEST')
yield call(updateResource, needle, data)
}
}
It just runs all watcher sagas in parallel:
export default function* () {
yield fork(watchResourceCreateRequest)
yield fork(watchResourceListReadRequest)
yield fork(watchResourceDetailReadRequest)
yield fork(watchResourceUpdateRequest)
yield fork(watchResourceDeleteRequest)
}
Even though they perform side effects, sagas are written in a way that they can be considered as pure functions. And, such as actions
and reducers
, unit testing sagas is easy:
test('updateResource', () => {
const data = { id: 1 }
const generator = updateResource(1, { title: 'foo' })
expect(generator.next().value).toEqual(call(api.put, '/resources/1', { title: 'foo' }))
expect(generator.next(data).value).toEqual(put(resourceUpdateSuccess(1, data)))
})
Writing unit tests for sagas is easy, but one could complain about it being too coupled to its implementation. That is, it's very hard to do TDD with it.
A solution could be writing integration tests instead of unit ones. With the help of redux-saga-test-plan
, you can test the whole flow through the saga:
it('calls success', () => {
// downside: we need to mock api
const api = {
put: (url, data) => Promise.resolve({ id: 1, ...data }),
}
// expect saga to call that endpoint and dispatch that action when I dispatch it
return expectSaga(saga, api)
.call([api, api.put], '/resources/1', { foo: 'bar' })
.put(resourceUpdateSuccess(1, { id: 1, foo: 'bar' }))
.dispatch(resourceUpdateRequest(1, { foo: 'bar' }))
.run({ timeout: 20, silenceTimeout: true })
})
Special thanks to @kybarg and @protoEvangelion for helping to write this Wiki. Please, feel free to edit/create pages if you think it might be useful (also, see #33)