Skip to content
Diego Haz edited this page May 19, 2017 · 2 revisions

ARc uses redux-saga to perform side effects based on dispatched actions.

Basically, you will see 3 types of sagas on sagas.js files: root saga, watcher sagas and worker sagas.

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))
  }
}

Watcher sagas

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)
  }
}

Root saga

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)
}

Unit testing sagas

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)))
})

Integration testing sagas

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 })
})