Подтвердить что ты не робот

Как протестировать ошибки API с помощью Redux Saga?

Я пытаюсь проверить каждый сценарий, который могла бы выполнить моя сага, но я не могу сделать, это поведение, которое я хочу. Это довольно просто, у меня есть HTTP-запрос (логин), и я хочу проверить успех и случаи сбоев, издеваясь над моим методом API.

Но, похоже, что call effect не запускает мою функцию api, я пока не понимаю, как это работает, но я предполагаю, что промежуточное программное обеспечение отвечает за вызов функции, и поскольку я не знаю, t идти, хотя хранилище на моем тесте, я не могу получить результат.

Итак, мой вопрос: как вы можете проверить свою сагу, когда вам нужно отправить различные действия (обычно успех или сбой) рядом с вашим асинхронным вызовом?

Я искал пример, я нашел sagas с успехом и потерпел неудачу, но случай сбоя никогда не тестировался, например, в примере корзины

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
    });
  });
});
4b9b3361

Ответ 1

Отметить ответ. Средство выполнения выполняет эти инструкции. Но это облегчает вашу жизнь: в тесте вы можете предоставить все, что хотите, в качестве аргумента next(), а функция генератора получит его в результате yield. Это именно то, что делает промежуточное программное обеспечение саги (за исключением того, что он фактически запускает запрос вместо того, чтобы давать вам фальшивый ответ).

Чтобы сделать 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' }
    })
  );
});

Ответ 2

Правильно. Насколько я понимаю, весь смысл Redux-Saga заключается в том, что ваша функция саги использует API-интерфейсы саги для возврата объектов, описывающих действие, а затем промежуточное ПО позже смотрит на эти объекты, чтобы фактически выполнить поведение. Таким образом, оператор yield call(myApiFunction, "/someEndpoint", arg1, arg2) в саге может возвращать объект, который по идее выглядит как {effectType : CALL, function: myApiFunction, params: [arg1, arg2]}.

Вы можете проверить источник редукции-саги, чтобы увидеть, как именно выглядят эти декларативные объекты, и создать соответствующий объект для сравнения в вашем тесте или использовать сами функции API для создания объектов (что я думаю, что redux-saga делает в своем тестовом коде).

Ответ 3

Вы также можете использовать вспомогательную библиотеку для проверки своих Sagas, например redux-saga-testing.

Отказ от ответственности: я написал эту библиотеку для решения этой же проблемы

Эта библиотека сделает ваш тест похожим на любой другой (синхронный) тест, о котором гораздо проще рассуждать, чем вызов 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]' })));
    });
});

Другие примеры (с использованием Jest, Mocha и AVA) на GitHub.