如何用jest测试redux saga?

时间:2017-12-30 10:46:58

标签: reactjs testing jest redux-saga

刚刚做出反应,react-redux / saga和jest

考虑:

----- Componnent()----

componentDidMount() {

    this.props.actions.initTodos(
        axios,
        ajaxURLConstants.WP_GET_TODOS,
        appStateActions.setAppInIdle,
        appStateActions.setAppInProcessing,
        todosActions.todosInitialized
    );

}

因此,当我的 TodoApp 组件安装时,它会调度 INIT_TODOS 操作,然后我的root传奇正在侦听,并且当它被捕获时它会产生适当的工人传奇以便采取相应的行动。

-----相应的工作者传奇-----

export function* initTodosSaga( action ) {

    try {

        yield put( action.setAppInProcessing() );

        let response = yield call( action.axios.get , action.WP_GET_TODOS );

        if ( response.data.status === "success" )
            yield put( action.todosInitialized( response.data.todos ) );
        else {

            console.log( response );
            alert( response.data.error_msg );

        }

    } catch ( error ) {

        console.log( "error" , error );
        alert( "Failed to load initial data" );            

    }

    yield put( action.setAppInIdle() );

}

-----迄今为止的测试-----

import todos             from "../../__fixtures__/todos";
import { initTodosSaga } from "../todosSaga";

test( "saga test" , () => {

    let response = {
            status : "success",
            todos
        },
        action = {
            axios : {
                get : function() {

                    return new Promise( ( resolve , reject ) => {

                        resolve( response );

                    } );

                }
            },
            WP_GET_TODOS       : "dummy url",
            setAppInIdle       : jest.fn(),
            setAppInProcessing : jest.fn(),
            todosInitialized   : jest.fn()
        };

    let initTodosSagaGen = initTodosSaga( action );

    initTodosSagaGen.next();

    expect( action.setAppInIdle ).toHaveBeenCalled();

} );

-----测试结果-----

enter image description here

所以重要的是这个

console.error node_modules \ redux-saga \ lib \ internal \ utils.js:240

在check put(action)中未被捕获:参数操作未定义

但是我有console.log我在测试中传递给我的测试的行动,实际上它并不是未定义的

我错过了什么?

提前致谢。

---------- ------------更新

在顶部注意到它正在抱怨这行代码

yield put( action.setAppInIdle() );

哪个在try catch块之外,所以我进行了一些更改

1。)我将上面的代码移到try catch块中,就在

的else语句之后
if ( response.data.status === "success" )

请检查上面的 initTodosSaga 代码

然后在我的传奇测试中,我测试了

expect( action.setAppInProcessing ).toHaveBeenCalled();

而不是 setAppInIdle 间谍功能

这是测试结果

enter image description here

所以测试通过了! 但仍抱怨行动未定义

现在有趣的是,如果在我的传奇测试中,如果我现在测试

expect( action.setAppInProcessing ).toHaveBeenCalled();
expect( action.setAppInIdle ).toHaveBeenCalled();

这是结果

enter image description here

所以现在它仍然抱怨动作仍然未定义(我没有包含在我的截图中,但仍然与上面相同)

加上关于 setAppInIdle 的第二个断言没有调用spy函数,但 setAppInProcessing 确实通过了!

我希望这些额外的信息有助于解决这个问题。

2 个答案:

答案 0 :(得分:1)

在没有任何外部库的帮助的情况下测试redux saga似乎非常困难

对我来说,我用过 https://github.com/jfairbank/redux-saga-test-plan

这个图书馆非常好。

所以这是我现在的测试

-------------------- Test 1 --------------------- < /强>

因此,对于这个测试,我传递了几乎所有saga需要它运行的动作有效负载,例如。 axios,动作创造者功能等...... 更像是遵循依赖注入的原则,因此很容易测试。

----- TodoApp组件-----

componentDidMount() {

    this.props.actions.initTodos(
        axios,
        ajaxURLConstants.WP_GET_TODOS,
        appStateActions.setAppInIdle,
        appStateActions.setAppInProcessing,
        todosActions.todosInitialized,
        todosActions.todosFailedInit
    );

}

因此,当组件安装时,会触发我的根传奇监听并捕获的操作,然后生成相应的工作者传奇以进行相应的操作

再次注意我传递了工作者传奇需要在动作有效负载上正常运行的所有必要数据。

----- initTodoSaga(工作者传奇)-----

export function* initTodosSaga( action ) {

    try {

        yield put( action.setAppInProcessing() );

        let response = yield call( action.axios.get , action.WP_GET_TODOS );

        if ( response.data.status === "success" )
            yield put( action.todosInitialized( response.data.todos ) );
        else {

            console.log( response );
            alert( response.data.error_msg );

            yield put( action.todosFailedInit( response ) );

        }

    } catch ( error ) {

        console.log( "error" , error );
        alert( "Failed to load initial data" );

        yield put( action.todosFailedInit( error ) );

    }

    yield put( action.setAppInIdle() );

}

-----佐贺测试-----

import { expectSaga }    from "redux-saga-test-plan";
import { initTodosSaga } from "../todosSaga";

test( "should initialize the ToDos state via the initTodoSaga" , () => {

    let response = {

            data : {
                status : "success",
                todos
            }

        },
        action = {
            axios : {
                get : function() {

                    return new Promise( ( resolve , reject ) => {

                        resolve( response );

                    } );

                }
            },
            WP_GET_TODOS       : "dummy url",
            setAppInIdle       : appStateActions.setAppInIdle,
            setAppInProcessing : appStateActions.setAppInProcessing,
            todosInitialized   : todosStateActions.todosInitialized,
            todosFailedInit    : todosStateActions.todosFailedInit
        };

    // This is the important bit
    // These are the assertions
    // Basically saying that the actions below inside the put should be dispatched when this saga is executed
    return expectSaga( initTodosSaga , action )
        .put( appStateActions.setAppInProcessing() )
        .put( todosStateActions.todosInitialized( todos ) )
        .put( appStateActions.setAppInIdle() )
        .run();

} );

我的测试通过了! :) 现在,当测试失败时向您显示错误消息,我将在 initTodosSaga

中注释掉这行代码
yield put( action.setAppInIdle() );

所以现在断言

.put( appStateActions.setAppInIdle() )

现在应该失败

enter image description here

所以它输出把期望未满足这是有意义的,因为我们预期会被解雇的行动没有

--------------------测试2 --------------------

现在这个测试是针对一个传奇,它导入了一些它需要操作的东西,不像我的第一个测试,我在动作有效载荷中提供axios,动作创建者

这个传奇导入了axios,它需要操作的动作创作者

谢天谢地 Redux Saga测试计划有一些辅助功能,可以“提供”虚拟数据到传奇中

我将跳过触发根传奇正在侦听的动作的组件,它不重要,我将直接粘贴传奇和传奇测试

<强> ---- ---- addTodoSaga

/** global ajaxurl */
import axios                from "axios";
import { call , put }       from "redux-saga/effects";
import * as appStateActions from "../actions/appStateActions";
import * as todosActions    from "../actions/todosActions";

export function* addTodoSaga( action ) {

    try {

        yield put( appStateActions.setAppInProcessing() );

        let formData = new FormData;

        formData.append( "todo" , JSON.stringify( action.todo ) );

        let response = yield call( axios.post , ajaxurl + "?action=wptd_add_todo" , formData );

        if ( response.data.status === "success" ) {

            yield put( todosActions.todoAdded( action.todo ) );
            action.successCallback();

        } else {

            console.log( response );
            alert( response.data.error_msg );

        }

    } catch ( error ) {

        console.log( error );
        alert( "Failed to add new todo" );

    }

    yield put( appStateActions.setAppInIdle() );

}

-----测试-----

import axios          from "axios";
import { expectSaga } from "redux-saga-test-plan";
import * as matchers  from "redux-saga-test-plan/matchers";
import * as appStateActions   from "../../actions/appStateActions";
import * as todosStateActions from "../../actions/todosActions";
import { addTodoSaga } from "../todosSaga";

test( "should dispatch TODO_ADDED action when adding new todo is successful" , () => {

   let response = {
            data : { status : "success" }
        },
        todo = {
            id        : 1,
            completed : false,
            title     : "Browse 9gag tonight"
        },
        action = {
            todo,
            successCallback : jest.fn()
        };

    // Here are the assertions
    return expectSaga( addTodoSaga , action )
        .provide( [
            [ matchers.call.fn( axios.post ) , response ]
        ] )
        .put( appStateActions.setAppInProcessing() )
        .put( todosStateActions.todoAdded( todo ) )
        .put( appStateActions.setAppInIdle() )
        .run();

} );

因此,提供函数允许您模拟函数调用,同时提供应该返回的虚拟数据

就是这样,我现在能够测试我的传奇!耶!

还有一件事,当我为我的传奇运行测试时,导致执行带有警报代码的代码

离。

alert( "Earth is not flat!" );

我在控制台上得到了这个

Error: Not implemented: window.alert

和它下面的一堆堆栈跟踪,所以也许是因为节点上没有警报对象?我怎么隐藏这个?如果你们有答案,请加上评论。

我希望这有助于任何人

答案 1 :(得分:1)

以下是您的测试的工作版本:

import todos from '../../__fixtures__/todos';
import { initTodosSaga } from '../todosSaga';
import { put, call } from 'redux-saga/effects';

test('saga test', () => {
    const response = {
        data: {
            status: 'success',
            todos
        }
    };
    const action = {
        axios: {
            get() {}
        },
        WP_GET_TODOS: 'dummy url',
        setAppInIdle: jest.fn().mockReturnValue({ type: 'setAppInIdle' }),
        setAppInProcessing: jest.fn().mockReturnValue({ type: 'setAppInProcessing' }),
        todosInitialized: jest.fn().mockReturnValue({ type: 'todosInitialized' })
    };
    let result;

    const initTodosSagaGen = initTodosSaga(action);

    result = initTodosSagaGen.next();
    expect(result.value).toEqual(put(action.setAppInProcessing()));
    expect(action.setAppInProcessing).toHaveBeenCalled();
    result = initTodosSagaGen.next();
    expect(result.value).toEqual(call(action.axios.get, action.WP_GET_TODOS));
    result = initTodosSagaGen.next(response);
    expect(action.todosInitialized).toHaveBeenCalled();
    expect(result.value).toEqual(put(action.todosInitialized(response.data.todos)));
    result = initTodosSagaGen.next();
    expect(action.setAppInIdle).toHaveBeenCalled();
    expect(result.value).toEqual(put(action.setAppInIdle()));
});

一些注意事项:

  • 你实际上不必让模拟Axios.get返回任何东西
  • 使用expect语句,我将生成器的产量与我期望的生成器进行比较(即执行putcall语句)
  • 模拟回复中缺少data属性