ladjs / supertest

🕷 Super-agent driven library for testing node.js HTTP servers using a fluent API. Maintained for @forwardemail, @ladjs, @spamscanner, @breejs, @cabinjs, and @lassjs.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Error thrown from assertion function is being swallowed?

colincollerlever opened this issue · comments

I'm trying to write an assertion function per the documentation but the Error that I'm throwing from the assertion function is being swallowed.

Here's an extremely simple repo:

import * as supertest from 'supertest';

describe('An extremely simple test case', () => {
  const request: supertest.SuperTest<supertest.Test> = global.__REQUEST__;

  it("should fail", async () => {
    await request.get('/some/path').expect(() => {
      console.log(
        "This function gets called...",
      );
      throw new Error("...but it doesn't fail the test!"); // <- This Error gets swallowed!
    });
  });
});

When I run the test, the assertion function is called, but the Error is swallowed, and the test passes:

 PASS  test/e2e/specs/test.spec.ts
  ● Console

    console.log
      This function gets called...

      at specs/test.spec.ts:8:15


Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total

This is with Node 14.20.0, Typescript 4.7.4 (targeting ES2017), Jest 28.1.3, and Supertest 6.1.3.

The only difference that I can see between what I'm doing and what it shows in the documentation is that the documentation is explicitly calling .end(cb), while I'm using async/await and so don't have a callback.

Walking through the code in the debugger, it looks like the err caught by wrapAssertFn()'s inner function isn't an instanceof Error:

function wrapAssertFn(assertFn) {
  const savedStack = new Error().stack.split('\n').slice(3);

  return function(res) {
    let badStack;
    let err;
    try {
      err = assertFn(res);
    } catch (e) {
      err = e;
    }
    if (err instanceof Error && err.stack) {      <-- `err instanceof Error` evaluates to false here

and so the return value in _assertFunction() isn't an Error

  _assertFunction(fn, res) {
    let err;
    try {
      err = fn(res);
    } catch (e) {
      err = e;
    }
    if (err instanceof Error) return err;      <-- `err instanceof Error` evaluates to false here as well
  }

and so errorObj never gets set in assert()

...
    // asserts
    for (let i = 0; i < this._asserts.length && !errorObj; i += 1) {
      errorObj = this._assertFunction(this._asserts[i], res);
    }

    // set unexpected superagent error if no other error has occurred.
    if (!errorObj && resError instanceof Error && (!res || resError.status !== res.status)) {
      errorObj = resError;
    }

    fn.call(this, errorObj || null, res);
  }

but at the moment I have no idea why it wouldn't be instanceof Error. 😞

I figured it out.

I'm setting up the SuperTest request via Jest's globalSetup. Jest runs the global setup in a different context than the tests. This means the SuperTest request is set up in a different context than the tests. When the test code throws an Error, and the SuperTest request catches it and checks whether it's instanceof Error, SuperTest is comparing 2 different Error types from the 2 different contexts. The comparison evaluates to false, which causes the error to be swallowed, and the test not to fail.