2

In all the examples I've found everyone is using yield call(), yield put(), etc. on their sagas. Right now I have a saga that just executes a function without using yield call(). This function is executed after a select effect and before a call effect (see code below for the service variable. This function returns an instance of a class, it's not a network request or a promise.

The saga works fine, but I'm not sure how to test it like this. Using redux-sagas-test-plan while having the effects works fine, but as soon as I remove the effect (which I remove from the .provide(), the test fails.

Saga

export function* getDetails() {
    try {
        const config = yield select(getProperties());
        const service = getService(config);
        const data = yield call([service, service.getDetails]);
        yield put(success(data));
    } catch(e) {
        yield put(failure());
    }
}

Test

import { getDetails as detailsSaga } from '...';
const data = {};

it('should succeed getting details', async () => {
        await expectSaga(detailsSaga)
            .provide([
                [select(getProperties), {}],
                [call([serviceMock, serviceMock.getDetails]), data]
            ])
            .put(success(data))
            .dispatch(fetchDetails())
            .silentRun();
    });

The expected result from the test is tu have the success(data) creator to execute but instead I get the failure() creator as the actual value.

Expected
    --------
    { '@@redux-saga/IO': true,
      combinator: false,
      type: 'PUT',
      payload: 
       { channel: undefined,
         action: 
          { type: 'FETCH_DETAILS_SUCCESS',
            data: { } } } }

    Actual:
    ------
    1. { '@@redux-saga/IO': true,
      combinator: false,
      type: 'PUT',
      payload: 
       { channel: undefined,
         action: { type: 'FETCH_DETAILS_FAILURE' } } }
Ostos
  • 134
  • 1
  • 8
  • I'm assuming `service.getDetails` is making a network request? You may want to mock the network request with something like `jest-fetch-mock`: https://github.com/jefflau/jest-fetch-mock to return the expected response. When you use the provider you're skipping entirely whatever is happening in `service.getDetails` which is also fine if you don't care about testing the implementation of `service.getDetails` in this particular test. – azundo Sep 03 '19 at 21:54

2 Answers2

2

Based on other examples I found, this was my solution:

import { call } from 'redux-saga-test-plan/matchers';

beforeEach(() => {
  service = getService({});
  data = { /* ... */ });
});

it('should succeed getting details', async () => {
  await expectSaga(detailsSaga)
    .provide([
      [select(getProperties), {}],
      [call.fn(service.getDetails), data]
    ])
    .put(success(data))
    .dispatch(fetchDetails())
    .silentRun();
});

I have to use the call effect from redux-saga-test-plan/matchers and create an actual instance returned by getService. For some reason a mock doesn't work here.

The details of why this works are not that clear to me, but I'm writing this answer just in case someone is trying to achieve this as well. Note: I agree with the other answer, that all functions should be called using yield call but I've been asked not to do that here.

Ostos
  • 134
  • 1
  • 8
1

Anytime you call a function in a saga, you should use the call effect. It would be best to use that instead of directly calling the function as you have done. It would return the same data as when you call it directly, and would make testing it easier as you don't have to mock or spy on anything.

Example:

export function* getDetails() {
    try {
        const config = yield select(getProperties());
        const service = yield call(getService, config);
        const data = yield call([service, service.getDetails]);
        yield put(success(data));
    } catch(e) {
        yield put(failure());
    }
}
Tameem Safi
  • 728
  • 5
  • 6
  • I agree with you, that's what I was doing, but I'm being asked not to do that. The getService method is just a regular function that returns an instance of a class based on the config values. It's not a network request or a promise even. People in my office are telling me that getService is a "function not a saga" and that we should be treating it as a regular function. So literally, I'm being asked to remove the `yield call` I had just as you mentioned. – Ostos Sep 04 '19 at 14:28
  • No problem, the `call` effect is created to call any type of function. It also supports calling methods from within classes and passing the `this` context. It is not just for calling promises, or other sagas. Technically any function should be used with it as it makes testing easier. – Tameem Safi Sep 06 '19 at 12:54