supertest: Optionally print response body/text on failure

If I get a wrong status code back from a controller, it’d be nice if supertest had an optional ‘print response body on failure’ mode so I can see the content of the message associated with that wrong status code. Currently you have to go in and manually add additional debugging into like: .expect(200, function() { console.log(arguments); done(); }) every time a test starts failing.

Another option might be to be smarter about generating the Error response in supertest, e.g. parsing the res.body and (optionally) jamming that into the error message. It could even somehow do some specialcase handling of the default connect error handler to populate the stack trace (if that’s even possible).

About this issue

  • Original URL
  • State: closed
  • Created 12 years ago
  • Reactions: 5
  • Comments: 22 (1 by maintainers)

Most upvoted comments

meh. just console.log.

If anyone stumbles upon this and wonders how to log at all. Appending this to your test case logs if an error occurs and rethrows the error via done.

        .end((err, res) => {
          if (err) {
            console.error(res.error);
          }
          done(err);
        });

If anyone is still looking for a way to do this with Promises here’s how I did it. According to the readme, “Expectations are run in the order of definition.”, so I just created an expection before the expect(statusCode) that checks if the res is the correct statusCode, and if not prints out the body.

For example:

request(server)
  .get(endpoint)
  .query(query)
  .set('Accept', 'application/json; charsaet=utf-8')
  .expect(function(res) {
    if(res.status != statusCode){
      console.log(JSON.stringify(res.body, null, 2));
     }
  })
  .expect(statusCode);

What if I’m using promises? Putting something like this in the catch block only gives me the generic error that isn’t helpful. I can’t call .end when using promises.

The solution proposed by @andrewtran1995 wasn’t working for me either and after debugging I found out that the error stack trace was being logged, so just needed to replace the message from the stack and it work!

import supertest from 'supertest';

declare module 'supertest' {
  interface Test {
    _assert(this: supertest.Test, resError: Error, res: supertest.Response, fn: Function)
  }
}

Object.defineProperties((supertest as any).Test.prototype, {
  _assert: {
    value: (supertest as any).Test.prototype.assert,
  },
  assert: {
    value: function (this: supertest.Test, resError, res, fn) {
      this._assert(resError, res, (err, res) => {
        if (err) {
          const originalMessage = err.message;
          err.message = `${err.message}\nstatus: ${res.status}\nresponse: ${JSON.stringify(res.body, null, 2)}`;
          // Must update the stack trace as what supertest prints is the stacktrace
          err.stack = err.stack?.replace(originalMessage, err.message);
        }
        fn.call(this, err, res);
      });
    }
  }
});

If you want something reusable and are using typescript, this what I did which is inspired by C# extension methods:

import supertest from 'supertest';

(supertest as any).Test.prototype.expectOrPrint = function (
  this: supertest.Test,
  statusCode: number
) {
  this.expect((res) => {
    if (res.status !== statusCode) {
      console.error(JSON.stringify(res.body, null, 2));
    }
  });
  this.expect(statusCode);
  return this;
};

declare module 'supertest' {
  interface Test {
    expectOrPrint: (statusCode: number) => Test;
  }
}

and then you can use it like this:

await request
  .post('/api/v1/graphql')
  .send({
    query: `{
      contacts(
        first: 1
      ) {
        id, firstName
      }
    }`,
  })
  .expectOrPrint(401);

I was looking for a solution to the same problem, and ended up making a VS Code snippet that would insert the following one-liner in my chain, for status code 204, as an example:

.expect((res) => (res.status != 204 ? console.log(res.body) : 0))

Similar to @strujillojr’s suggestion. My response body is already a JSON payload.

It would be opt-in. Basically, when debugging an error I want a clean way to see the associated error information without having to mess around adding console.logs in the test callback. Another idea would be to optionally pop open a web browser at the current url. These features would be useful to me, perhaps I need to start simply making addons to your modules instead of diluting the core.

Actually, one problem with that solution (and previous solutions mentioned) is that the response body is not printed along-side the expect failure. Depending on how many tests there are and/or how many of those are failing, it’s quite difficult to find the corresponding response body. I made the following modification which essentially replaces expect:

import supertest from 'supertest';
import status from 'statuses';

(supertest as any).Test.prototype.expect = function (
  this: supertest.Test,
  statusCode: number
) {
  this.then((res) => {
    if (res.status !== statusCode) {
      let message = `expected ${statusCode} "${status(statusCode)}", got ${res.status} "${status(res.status)}"
response: ${JSON.stringify(res.body, null, 2)}`;
      throw message;
    }
  });
  return this;
};

With this, doing a normal .expect(402) would print out something like the following:

image

solution proposed by @andrewtran1995 wasn’t working for us so we decided to use following snippet

Object.defineProperties((supertest as any).Test.prototype, {
  '_assertStatus': {
    value: function (this: supertest.Test, status, res) {
      if (res.status !== status) {
        const a = http.STATUS_CODES[status];
        const b = http.STATUS_CODES[res.status];
        return new Error(`expected ${status} "${a}", got ${res.status} "${b}" \nresponse: ${JSON.stringify(res.body, null, 2)}`);
      }
    }
  }
});

I found the above solutions limiting in terms of allowing chained expect calls, so I found an alternative that alters the assert method on the prototype instead.

import supertest from 'supertest';

declare module 'supertest' {
  interface Test {
    _assert(this: supertest.Test, resError: Error, res: supertest.Response, fn: Function)
  }
}

Object.defineProperties((supertest as any).Test.prototype, {
  '_assert': {
    value: (supertest as any).Test.prototype.assert,
  },
  'assert': {
    value: function (this: supertest.Test, resError, res, fn) {
      this._assert(resError, res, (err, res) => {
        if (err) {
          err.message = `${err.message}\nstatus: ${res.status}\nresponse: ${JSON.stringify(res.body, null, 2)}`;
        }
        fn.call(this, err, res);
      });
    }
  }
});

This approach has the advantages of:

  • The ability to chain calls as normal (e.g., multiple expect calls).
  • Not needing to overwrite or rewrite the existing expect assertion logic.

This results in error messages such as the following, which is output from a test that failed a response body checker.

Error: expect(received).toInclude(expected)

Expected string to include:
  "GraphQL is stopping"
Received:
  "GraphQL is running development server."
status: 200
response: {
  "data": {
    "hello": "GraphQL is running development server."
  }
}

All-in-all, I’d still love some ability to configure supertest to allow for automatic response printing on error.

+1. Or can someone show us how to console.log when .expect(statusCode) fails?

I like this idea. How can I console.log when an .expect(200) fails?