web-dev-qa-db-ja.com

Redux SagaでAPIリクエストの失敗をテストするにはどうすればよいですか?

サガが従う可能性のあるすべてのシナリオをテストしようとしていますが、希望する動作を実行できません。これは非常に簡単です。HTTPリクエスト(ログイン)があり、APIメソッドをモックして成功と失敗のケースをテストしたいと思います。

しかし、call effectがapi関数を起動しないようです。実際の動作はまだわかりませんが、関数の呼び出しはミドルウェアが担当していると思います。私のテストで店を行って、私は結果を得ることができません。

だから私の質問は、非同期呼び出しの横にさまざまなアクション(通常は成功または失敗)をディスパッチする必要があるときに、どのようにサガをテストできるかということです。

私は例を探しました、私はサガを成功と失敗で見つけましたが、失敗のケースはテストされていません、例えばショッピングカートの例 here

SAGA.JS

export function* login(action) {
  try {
    const user = yield call(api.login, action);
    return yield put(actions.loginSuccess(user));
  } catch(e) {
    yield put(actions.loginFail(e));
  }
}

export default function* rootAuthenticationSagas() {
  yield* takeLatest(LOGIN, login);
}

TEST.JS

describe('login', () => {
  context('When it fails', () => {
    before('Stub the api', () => {
      sinon.stub(api, 'login', () => {
        // IT NEVER COMES HERE !
        return Promise.reject({ error: 'user not found' });
      });
    });

    it('should return a LOGIN_FAIL action', () => {
      const action = {
        payload: {
          name: 'toto',
          password: '123456'
        }
      };
      const generator = login(action);

      // THE CALL YIELD
      generator.next();

      const expectedResult = put({ type: 'LOGIN_FAIL', payload: { error: 'user not found' } });
      expect(generator.next().value).to.be.eql(expectedResult); // FAIL BECAUSE I GET A LOGIN_SUCCESS INSTEAD OF A FAIL ONE
    });
  });
});
27
Ludo

マークの答え は正しいです。ミドルウェアはそれらの命令を実行します。しかし、これはあなたの人生をより簡単にします:テストでは、next()への引数として何でも提供でき、ジェネレーター関数はyieldの結果としてそれを受け取ります。これは、sagaミドルウェアが行うこととまったく同じです(ただし、偽の応答を返すのではなく、実際に要求を起動する場合を除きます)。

yieldに任意の値を取得させるには、その値をnext()に渡します。エラーを「受け取る」ようにするには、それをthrow()に渡します。あなたの例では:

it('should return a LOGIN_FAIL action', () => {
  const action = {
    payload: {
      name: 'toto',
      password: '123456'
    }
  };
  const generator = login(action);

  // Check that Saga asks to call the API
  expect(
    generator.next().value
  ).to.be.eql(
    call(api.login, action)
  );

  // Note that *no actual request was made*!
  // We are just checking that the sequence of effects matches our expectations.

  // Check that Saga reacts correctly to the failure
  expect(
    generator.throw({
      error: 'user not found'
    }).value
  ).to.be.eql(
    put({
      type: 'LOGIN_FAIL',
      payload: { error: 'user not found' }
    })
  );
});
43
Dan Abramov

正しい-私が理解しているように、Redux-Sagaの要点は、saga関数がsaga APIを使用してアクションを説明するオブジェクトを返し、ミドルウェアが後でそれらのオブジェクトを調べて実際に動作を実行することです。したがって、サガのyield call(myApiFunction, "/someEndpoint", arg1, arg2)ステートメントは、概念的には{effectType : CALL, function: myApiFunction, params: [arg1, arg2]}のようなオブジェクトを返す可能性があります。

Redux-sagaソースを検査して、これらの宣言型オブジェクトが実際にどのように見えるかを正確に確認し、テストで比較するために一致するオブジェクトを作成するか、API関数自体を使用してオブジェクトを作成することができます(これはredux-sagaが考えていることです)テストコードで行います)。

9
markerikson

redux-saga-testing などのヘルパーライブラリを使用してSagasをテストすることもできます。

免責事項:私はこのライブラリを作成して、まったく同じ問題を解決しました

このライブラリは、テストを他の(同期)テストのように見せます。これは、generator.next()を手動で呼び出すよりもずっと簡単に推論できます。

例を挙げれば、次のようにテストを書くことができます:

(Jest構文を使用していますが、Mochaと基本的に同じです。完全にテストライブラリに依存しません)

import sagaHelper from 'redux-saga-testing';
import { call, put } from 'redux-saga/effects';
import actions from './my-actions';
import api from './your-api';

// Your example
export function* login(action) {
    try {
        const user = yield call(api.login, action);
        return yield put(actions.loginSuccess(user));
    } catch(e) {
        yield put(actions.loginFail(e.message)); // Just changed that from "e" to "e.message"
    }
}


describe('When testing a Saga that throws an error', () => {
    const it = sagaHelper(login({ type: 'LOGIN', payload: 'Ludo'}));

    it('should have called the API first, which will throw an exception', result => {
        expect(result).toEqual(call(api, { type: 'LOGIN', payload: 'Ludo'}));
        return new Error('Something went wrong');
    });

    it('and then trigger an error action with the error message', result => {
        expect(result).toEqual(put(actions.loginFail('Something went wrong')));
    });
});

describe('When testing a Saga and it works fine', () => {
    const it = sagaHelper(login({ type: 'LOGIN', payload: 'Ludo'}));

    it('should have called the API first, which will return some data', result => {
        expect(result).toEqual(call(api, { type: 'LOGIN', payload: 'Ludo'}));
        return { username: 'Ludo', email: '[email protected]' };
    });

    it('and then call the success action with the data returned by the API', result => {
        expect(result).toEqual(put(actions.loginSuccess({ username: 'Ludo', email: '[email protected]' })));
    });
});

GitHub のその他の例(Jest、Mocha、AVAを使用)。

0