jfairbank / redux-saga-test-plan

Test Redux Saga with an easy plan.
http://redux-saga-test-plan.jeremyfairbank.com
MIT License
1.25k stars 127 forks source link

UnhandledPromiseRejectionWarning even with try/catch #123

Open chambo-e opened 7 years ago

chambo-e commented 7 years ago

Hello, I'm facing an issue testing failing saga. I'm able to run them successfully but errors are still bubbled up even if they are correctly catched.
Simple example below:

import { call, put } from 'redux-saga/effects';
import { runSaga } from 'redux-saga';
import { expectSaga } from 'redux-saga-test-plan';

const api = {
  resolve: () => Promise.resolve('resolve'),
  reject: () => Promise.reject(new Error('reject')),
};

function* resolveSaga(api) {
    yield call(api.resolve);
    yield put({ type: 'SUCCESS' });
}

function* rejectSaga(api) {
  try {
      yield call(api.reject);
      yield put({ type: 'SUCCESS' });
  } catch(error) {
    yield put({ type: 'ERROR' });
  }
}

it('resolves', () => {
    return expectSaga(resolveSaga, api)
    .put({
        type: 'SUCCESS',
    })
    .run();
});

it('resolves manually', () => {
  runSaga({ dispatch: () => undefined }, resolveSaga, api);
});

it('rejects with an uncaughtException!', () => {
    return expectSaga(rejectSaga, api)
    .put({
        type: 'ERROR',
    })
    .run();
});

it('rejects manually without an UnhandledPromiseRejectionWarning', () => {
  runSaga({ dispatch: () => undefined }, rejectSaga, api);
});

I don't know if what I do with runSaga is enough to have similar execution but this is the output:

screen shot 2017-06-02 at 18 24 10

Do you have any idea of how I could avoid the exception ?

Thanks 😉

jfairbank commented 7 years ago

Hi, @chambo-e!

I believe this is related to how it currently track any promises yielded by a saga. I originally added it to ensure that it knew when your saga was "done." However, I think we might be able to remove the promise checks and only go off the main tasks done promise instead.

I've been swamped the past couple weeks and will be next week, so it'll be a bit before I can investigate further. You're welcome to investigate before that, though.

binocarlos commented 7 years ago

I can confirm this issue with the following code:

function* notErrorCatching(opts = {}) {
  { actions, api, payload } = opts
  yield put(actions.request(payload))

  let apiResult = null, apiError = null

  try {
    const data = yield call(api, payload)
    yield put(actions.response(data))
    apiResult = data
  }
  catch(error) {
    yield put(actions.error(error))
    apiError = error
  }

  return {
    answer: apiResult,
    error: apiError
  }
}

the test:

return expectSaga(ApiSaga, opts)
    .put(getAction('request', { payload: REQUEST }))
    .put(getAction('error', { payload: ERROR }))
    .returns({
      answer: null,
      error: 'hello world'
    })
    .run()
    .then((result) => {
      t.ok('saga has passed')
      t.end()
    })

output:

$ tape -r babel-register src/template-ui/lib/plugins/api/saga.test.js
TAP version 13
# api saga: error
(node:794) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): hello world
(node:794) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process witha non-zero exit code.
[WARNING]: Saga exceeded async timeout of 250ms
ok 1 should be truthy

1..1
# tests 1
# pass  1

# ok

The test still passes though but the message persists