简体   繁体   English

如何通过模拟选择器和/或 redux 存储来编写 Redux Saga 测试

[英]How to write a Redux Saga Test by mocking selectors and / or redux store

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.上下文:我是编写 Redux Saga 测试的新手,并且一直在使用React Boilerplate开发一个应用程序,该应用程序使用 Jest 进行测试。 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.样板非常模块化和复杂,我什至无法弄清楚如何开始编写测试来模拟选择器和状态,以便我的 Saga 在测试中使用。

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.在 Saga 中,我使用 Reselect(在“./selectors”文件中)从减速器中获取“用户名”和“密码”,使用yield select将它们分配给一个常量,并使用它来运行 API我不认为我的问题在于测试传奇,而是模拟传奇中的状态和选择器来模拟登录信息已填写并可以从状态中获取。

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:基本上我正在寻找的是一种在测试中运行传奇的方法,以便在传奇内部,我可以“强制” loginParams 对象使用“fakeState”供选择器使用,因为显然状态不存在这里:

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.我一直在寻找redux-saga-tester作为测试传奇的一种手段,我只是想我不知道从哪里开始redux-saga-tester中的状态,我的传奇可以将常量分配给......我认为从那里我可以处理其余的。

Has anyone, especially if you've worked with the boilerplate, have any suggestions?有没有人,特别是如果你使用过样板,有什么建议吗?

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.我们在我们的应用程序中使用 redux-saga-tester,我们处理这个的方式不是通过传递一个假状态,我们使用 .next 方法,然后返回我们期望的特定测试的结果。 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.一般来说,saga 测试的一个痛点是,如果您的 saga 中有任何类型的控制流(if/else),那么全面测试所有内容的最佳策略似乎是为该 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一个我们将 isConnected 设置为 true(如上例所示),另一个我们将其设置为 false,看起来像这样

    //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如果您需要将有效负载传递给 saga,您可以使用 sagaHelper 函数处理它,如下所示

  it.next = sagaHelper(login({
     username: 'testUsername',
     password: 'testPassword',
     // etc
  }));

Hope that helps!希望有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM