Reputation: 61
Context: I'm brand new to writing Redux Saga tests and have been using the React Boilerplate to develop an app, which uses Jest for testing. The boilerplate is extremely modular and complex and I am having trouble figuring out how to even begin writing a test to mock selectors and state for my Saga to use within the test.
In the Saga, I'm using Reselect (within the './selectors' file) to grab the 'Username' and "Password' from the reducer, assign them to a constant using yield select
, and using that that to run an API call. I don't think my issue is in testing the saga per-say, but mocking a state and the selectors within the saga to mimic that the Login information has been filled out and can be grabbed from state.
import { takeLatest, call, put, select } from 'redux-saga/effects';
import { loginSuccess, loginFailed } from '../App/actions';
import { ON_LOGIN } from '../App/constants';
import {
makeSelectUsername,
makeSelectPassword,
} from './selectors';
import api from '../../utils/api';
// LISTENER
export default function* loginRequestListener() {
yield takeLatest(ON_LOGIN, login);
}
// WORKER
export function* login() {
const loginParams = {
username: yield select(makeSelectUsername()),
password: yield select(makeSelectPassword()),
isForceLoginAttempt: yield select(makeSelectIsForceLogin()),
};
try {
const user = yield call(api.user.login, loginParams);
yield put(loginSuccess(user));
} catch (error) {
yield put(loginFailed(error.response.data));
}
}
Basically what I am looking for is a way to run the saga in the test so that inside the saga, I can "force" the loginParams object to use the "fakeState" for the selectors to use instead since obviously the state doesn't exist here:
const fakeState = {username: 'test', password: 'test'}
export function* login() {
const loginParams = {
username: yield select(makeSelectUsername()) // 'test'
password: yield select(makeSelectPassword()) // 'test'
};
I've been looking at redux-saga-tester
as a means to test the saga, I just suppose I have no idea where to start in terms of mocking a state within the test that my saga can assign the constants to... I think from there I can handle the rest.
Has anyone, especially if you've worked with the boilerplate, have any suggestions?
Upvotes: 3
Views: 5612
Reputation: 14501
redux-saga-test-plan is the most popular library for testing sagas.
http://redux-saga-test-plan.jeremyfairbank.com/
Upvotes: 0
Reputation: 33
We use redux-saga-tester in our app, the way we've handled this is not by passing in a fake state, we use the .next method and then return what we expect the result to be for that particular test. See the cases below for what I'm describing.
const activeOrderId = '1111';
//returns the value for a isConnected boolean
it.next('should get isConnected', (result) => {
expect(result).toEqual(select(getIsConnected));
return true;
});
// returns the value for an activeOrderId
it.next('should get activeOrderId', (result) => {
expect(result).toEqual(select(getActiveOrderId));
return activeOrderId;
});`
The advantage of this method is that you don't have to define a large 'fake state' for all your sagas and that your testing of each saga is decoupled from that. You should have separate tests for your selectors anyway to ensure they are working correctly. A pain point of saga testing in general is that if you have any sort of control flow (if/else) in your saga then it seems the best strategy to test everything fully is to write out a whole other test flow for that saga. Say you have this
isConnected = yield select(getIsConnected)
if(isConnected) {
// do x
} else {
// do y
}
we've handled that by writing two describes. One where we'll set isConnected to true (as in the example above) and another where we set it to false which will look like this
//returns the value for a isConnected boolean
it.next('should get isConnected', (result) => {
expect(result).toEqual(select(getIsConnected));
return false;
});
If you need to pass in a payload to a saga, you can handle it with the sagaHelper function like this
it.next = sagaHelper(login({
username: 'testUsername',
password: 'testPassword',
// etc
}));
Hope that helps!
Upvotes: 2