【问题标题】:Redux-saga-test-plan expectSaga seems to retain state between independent testsRedux-saga-test-plan expectSaga 似乎在独立测试之间保留状态
【发布时间】:2021-10-13 06:09:18
【问题描述】:

我有以下两个测试

import {put, select, takeEvery} from 'redux-saga/effects';
import {combineReducers} from 'redux';

export default class SessionReducer {
    public static readonly _initialState: any = {
        disconnectCounts: {},
    };

    public static reducer(state: any = SessionReducer._initialState, action: any): any {
        // console.log('reducer', action);
        let newState: any;
        switch (action.type) {
            case 'DEVICE_DISCONNECTED':
                newState = {
                    ...state,
                };
                if (!newState.disconnectCounts[action.value]) newState.disconnectCounts[action.value] = 0;
                newState.disconnectCounts[action.value]++;
                newState.error = {
                    type: 'DEVICE_DISCONNECTED',
                    utc: 1,
                };

                return newState;
            default:
                return state;
        }
    }
}

export function* errorHandler() {
    yield takeEvery(['DEVICE_DISCONNECTED'], function* (action: any) {
        let state = yield select();
        console.log('*********', state);
        if (state.session.disconnectCounts[action.value] > 1) {
            yield put({
                type: 'WATCH_REBOOT_REQUEST',
            });
            // state.session.disconnectCounts[action.value] = 0
        }
    });
}
let action = {type: 'DEVICE_DISCONNECTED', value: '111'};
describe('Handles Error States and Transitions', () => {
    test('Sends watch reboot request when disconnection count threshold met', () => {
        return expectSaga(errorHandler)
            .withReducer(
                combineReducers({
                    session: SessionReducer.reducer,
                }),
                {session: SessionReducer._initialState},
            )
            .dispatch(action)
            .dispatch(action)
            .put({type: 'WATCH_REBOOT_REQUEST'})
            .run()
            .then((result: {storeState: any}) => {
                debugger;

                let session = result.storeState.session;
                expect(session.disconnectCounts[action.value]).toBe(2); // values for error are tested in reducer test
                expect(session.error).toBeTruthy(); // values for error are tested in reducer test
            });
    });
    test('Does not send WATCH_REBOOT_REQUEST when threshold not met', () => {
        return expectSaga(errorHandler)
            .withReducer(
                combineReducers({
                    session: SessionReducer.reducer,
                }),
                {session: SessionReducer._initialState},
            )
            .dispatch(action)
            .run()
            .then((result: {storeState: any}) => {
                let session = result.storeState.session;
                expect(session.disconnectCounts[action.value]).toBe(1); // values for error are tested in reducer test
                // expect(session.currentScreen).toEqual('actionRequiredIdleScreen');
            });
    });
});

如果您独立运行每个测试,我使用 .only,它们通过但不使用 .only 运行它们,并且第二个测试总是失败,并且 disconnectCounts 中的值过多

  Handles Error States and Transitions
    ✓ Sends watch reboot request when disconnection count threshold met (263 ms)
    ✕ Does not send WATCH_REBOOT_REQUEST when threshold not met (258 ms)

  ● Handles Error States and Transitions › Does not send WATCH_REBOOT_REQUEST when threshold not met

    expect(received).toBe(expected) // Object.is equality

    Expected: 1
    Received: 3

      76 |             .then((result: {storeState: any}) => {
      77 |                 let session = result.storeState.session;
    > 78 |                 expect(session.disconnectCounts[action.value]).toBe(1); // values for error are tested in reducer test
         |                                                                ^
      79 |                 // expect(session.currentScreen).toEqual('actionRequiredIdleScreen');
      80 |             });
      81 |     });

      at __tests__/sagas/sagaStateIssue.ts:78:64
      at tryCallOne (node_modules/promise/lib/core.js:37:12)
      at node_modules/promise/lib/core.js:123:15
      at flush (node_modules/asap/raw.js:50:29)

我错过了什么?

【问题讨论】:

  • 需要更多细节,请创建一个最小的、可重现的示例。向我们展示传奇代码。请简化代码,去掉不相关的部分,定位问题。
  • @slideshowp2 谢谢,是的,我最初应该花时间这样做。感谢收看。

标签: javascript redux redux-saga redux-toolkit redux-saga-test-plan


【解决方案1】:

将 reducer 和 state 放在一个类中是 redux 的反模式。

const initialState = () => ({ disconnectCounts: {} });
const reducer = (state: any = initialState(), action: any): any => {

您正在为 initialState 保留一个引用 最好有一个返回新实例的函数

https://codesandbox.io/s/proud-morning-0w4wu?file=/src/testy.test.ts:175-182

这是一个正在运行测试的沙盒

【讨论】:

  • 您能否详细说明您的反模式评论?我们使用 Redux Toolkit 发布的 reducer 模式;我以为那是官方的?
  • @RobelRobelLingstuyl Redux 工具包中的两种模式利用了它们拥有的两个主要工具。哪些是 createReducer ,它使用构建器函数进行回调。另一个是 createSlice,它将在对象中生成 actionCreators。将 reducer 放在类中没有任何好处,除了这里,我在任何地方都没有看到过这种模式。
【解决方案2】:

我认为问题在于两个测试都使用相同的引用 SessionReducer._initialState。当您将它传递给withReducer 中的状态时,它不会以任何方式克隆,因此您最终会在内存中使用相同的对象。

有很多方法可以解决它,例如您可以使用方法而不是属性来创建初始对象:

_initialState = () => ({disconnectCounts: {}})
// ...
.withReducer(
  combineReducers({
    session: SessionReducer.reducer,
  }),
  {session: SessionReducer._initialState()},
)

或者你可以在测试中自己深度克隆对象

const deepClone = obj => JSON.parse(JSON.stringify(obj))
// ...
.withReducer(
  combineReducers({
    session: SessionReducer.reducer,
  }),
  {session: deepClone(SessionReducer._initialState)},
)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-01-22
    • 2017-04-07
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-29
    相关资源
    最近更新 更多