'chai-as-promised rejection doesn't fail test but only print warning instead

I'm using chai-as-promised to test my readIndex(path): string function.

readIndex() returns a promise which in turn try to open and parse to JSON a file named index.json in target folder.

See the following extract :

// readIndex.js
module.exports = path =>
  new Promise((resolve, reject) => {
    try {
      const buffer = fs.readFileSync(path + '/index.json')
      const data = JSON.parse(buffer.toString())
      resolve(data)
    } catch (err) {
      if (err.code === 'ENOENT') {
        reject(Error('file not found'))
      } else if (err instanceof SyntaxError) {
        reject(Error('format not json'))
      } else {
        reject(err)
      }
    }
  })

With my case test's mock, the returned promise reject with the error "file not found".

But actually I'm testing with a (supposed to be) valid case, that should pass only if the promise resolved succesfully ...

At least this is what I understood of the promise.should.be.fulfilled usage.

See the test in question :

// readIndex.test.js
chai.use(chaiAsPromised)
chai.should()

describe('SUCCESS :', () => 
  it('should resolve with a (markdown) string extrapoled from target folder index file', done => {
    const key = 'content success'
    mock(_mocks[key])
    const promise = readIndex('test')
    promise.should.be.fulfilled
    mock.restore()
    done()
  }))

With such a setup, running the test does not make it fail; it prints this message instead :

    SUCCESS :
          √ should resolve with a (markdown) string extrapoled from target folder index file
    (node:183516) UnhandledPromiseRejectionWarning: AssertionError: expected promise to be fulfilled but it was rejected with 'Error: file not found'
    (node:183516) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 10)
    (node:183516) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Of course, expected outcome should be a failed test run, and something like this in the console :

SUCCESS :
         1) should resolve with a (markdown) string extrapoled from target folder index file:

      AssertionError: expected '1' to equal '2'

This strange behaviour even lead to (funny and) incoherent warnings.

Using promise.should.not.be.rejected, I got "expected promise not to be rejected but it was rejected" but test still passed :

SUCCESS :
      √ should resolve with a (markdown) string extrapoled from target folder index file
(node:225676) UnhandledPromiseRejectionWarning: AssertionError: expected promise not to be rejected but it was rejected with 'Error: file not found'

Actually my thoughts are that :

  • A solution would to increase the level of test fail to warnings, but I didn't found it in the chai-as-promised documentation.

  • Another solution would be to understand what layer is catching the Error/rejection, and is lowering it to a warning. Maybe a chai-as-promised default parameter ?



Solution 1:[1]

I just came across this little fact in the chai-as-promised documentation :

The test "line" (I don't have better word) must be preceeded by a return statement.

Let's take a look at their first example :

return doSomethingAsync().should.eventually.equal("foo")

The following is also very interesting :

or if you have a case where return is not preferable (e.g. style considerations) or not possible (e.g. the testing framework doesn’t allow returning promises to signal asynchronous test completion), then you can use the following workaround (where done() is supplied by the test framework):

doSomethingAsync().should.eventually.equal("foo").notify(done);

It did the trick for me.

I hope it'll help people.

Solution 2:[2]

I initially was using chai-as-promised there was some issue with promises not being correctly awaited that lead to hours of debugging my tests.

In the end I rolled my own solution.

EDIT v2 - more concise

// test-utils.ts

import { expect } from "chai";

export const expectRejection = async (
  promise: Promise<any>,
  errMsg: string
): Promise<void> => {
  try {
    await promise;
  } catch (err) {
    expect(err.message).eq(errMsg);
    return;
  }
  throw Error('Promise was not rejected');
};

You can use the function like this in your tests

it('Function that fails', async () => {
  const errMsg = 'My example';
  const functionThatRejects = async () => {
    throw Error(errMsg);
  };
  await expectRejection(functionThatRejects(), errMsg);
});

And for completeness here is a unit test for the function.

import { expectRejection } from './test-utils';
import { expect } from 'chai';

describe('test-utils.ts', () => {
  describe('expectRejection(..)', () => {
    it('Function that fails', async () => {
      const errMsg = 'My example';
      const functionThatRejects = async () => {
        throw Error(errMsg);
      };
      await expectRejection(functionThatRejects(), errMsg);
    });

    it("Function that doesn't fail", async () => {
      const functionThatResolves = async () => null;
      try {
        await expectRejection(functionThatResolves(), "Random error message");
      } catch (err) {
        expect(err).match(/Promise was not rejected/);
        return;
      }
      throw Error('Should have already returned');
    });
  });
});

v1 - more flexable

// test-utils.ts

import { expect } from "chai";

export const expectRejection = async (promise: Promise<any>) => {
  try {
    await promise;
  } catch (err) {
    return expect(err);
  }
  throw Error('Promise was not rejected');
};

You can use the function like this in your tests

it('Function that fails', async () => {
  const functionThatRejects = async () => {
    throw Error('My example');
  };
  const rejection = await expectRejection(functionThatRejects());
  rejection.match(/My example/);
});

And for completeness here is a unit test for the function.

// test-utils.spec.ts

import { expectRejection } from './test-utils';
import { expect } from 'chai';

describe('expectRejection(..)', () => {
  it('Function that fails', async () => {
    const functionThatRejects = async () => {
      throw Error('My example');
    };
    const rejection = await expectRejection(functionThatRejects());
    rejection.match(/My example/);
  });

  it("Function that doesn't fail", async () => {
    const functionThatResolves = async () => null;
    try {
      await expectRejection(functionThatResolves());
    } catch (err) {
      expect(err).match(/Promise was not rejected/);
      return;
    }
    throw Error('Should have already returned');
  });
});


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 imrok
Solution 2