web-dev-qa-db-ja.com

Redux Sagaをjestでテストするには?

React、react-redux/sagaおよびjestの新機能

考慮してください:

-----コンポーネント()----

componentDidMount() {

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

}

したがって、私のTodoAppコンポーネントがマウントされたとき、INIT_TODOSアクションをディスパッチしますその後、私のルートサガはリッスンし、それをキャッチすると、それに応じて行動する適切なワーカーサガを生成します。

-----対応する労働者佐賀-----

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

チェックput(action)でキャッチされない:引数actionは未定義です

しかし、私はconsole.logにワーカーサガ内のテストで渡したアクションを持っていますが、実際には未定義ではありません

私は何が欠けていますか?

前もって感謝します。

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

OK、このコード行で文句を言っていることに注意してください

yield put( action.setAppInIdle() );

Try catchブロックの外側にあるため、いくつかの変更を加えました

1.)上記のコードをtry catchブロック内で、elseステートメントの直後に移動しました

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

上記のコードinitTodosSagaを確認してください

それから私のサガテストで、私はテストします

expect( action.setAppInProcessing ).toHaveBeenCalled();

setAppInIdlespy関数の代わりに

これがテスト結果です

enter image description here

テストに合格しました! しかし、それでもアクションが未定義であることに文句を言っています

面白いのは、私のサガテストで、今これをテストするかどうかです

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

これが結果です

enter image description here

そのため、まだ定義されていないアクションについて不平を言っています(スクリーンショットには含まれていませんが、上記と同じです)

プラス2番目のアサートについてsetAppInIdlespy関数は呼び出されませんでしたが、setAppInProcessing合格しました!

この追加情報がこの質問の解決に役立つことを願っています。

8
Jplus2

外部ライブラリの助けなしにreduxの物語をテストすることは非常に難しいようです

私は https://github.com/jfairbank/redux-saga-test-plan を使用しました

このライブラリは非常に優れています。

ここに私のテストがあります

--------------------テスト1 ------------------- -

そのため、このテストでは、サガが機能するために必要なほぼすべてのアクションペイロードを渡しました。 axios、アクションクリエーター関数など...依存性注入の原理に従うので、テストが簡単です。

----- TodoAppコンポーネント-----

componentDidMount() {

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

}

そのため、コンポーネントがマウントされたときに、ルートサガがリッスンしてキャッチするアクションを起動し、適切なワーカーサガを生成して、それに応じて動作します

ここでも、ワーカーサーガがアクションペイロードで適切に操作するために必要なすべてのデータを渡すことに注意してください。

----- initTodoSaga(Worker Saga)-----

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

そのため、put expectation unmetを出力します。

--------------------テスト2 ------------------- -

このテストは、アクションペイロード内のアクションクリエーターであるaxiosにフィードする最初のテストとは異なり、操作に必要なものをインポートするサガ用です。

このサガはaxiosをインポートし、操作する必要があるアクションクリエーター

ありがたいことにRedux Sagaテスト計画には "feed"ダミーへのヘルパー関数があります佐賀へのデータ

ルートサガがリッスンしているアクションを実行するコンポーネントをスキップします。重要ではありません。サガとサガテストを直接貼り付けます。

---- 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();

} );

したがって、provide関数を使用すると、関数呼び出しをモックすると同時に、返す必要があるダミーデータを提供できます。

それだけです、今、私のサガをテストすることができます!わーい!

もう1つ、サガに対してテストを実行すると、アラートコード付きのコードが実行されます

例.

alert( "Earth is not flat!" );

コンソールでこれを入手しました

Error: Not implemented: window.alert

そして、その下にたくさんのスタックトレースがあります。ノードにアラートオブジェクトが存在しないためでしょうか?これをどうやって隠すの?回答があればコメントを追加してください。

これが誰にも役立つことを願っています

1
Jplus2

テストの作業バージョンは次のとおりです。

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ステートメントを使用して、ジェネレーターの歩留まりをジェネレーターに期待するものと比較しています(つまり、putおよびcallステートメントを実行します)
  • dataプロパティがモック応答にありませんでした
1
Patrick Hund