9

I'm using Supertest and Jest to test a Node.js API.

A sample test has the following format

it('Read a note for a user', (done) => {
      request(graphqlURL)
        .post('/graphql')
        .set(baseHeaders())
        .send({
          query: graphQLQuery
        })
        .end((err, res) => {
          expect(res.status).toBe(200);          
          done();
        })

    });

Currently when the expectation fails, the following gets logged

expect(received).toBe(expected) // Object.is equality

    Expected: 200
    Received: 404

I'd also like to log the request and the response alongside the failed tests to have more context while debugging.

Is there a way to print those as well, only for the tests which are failing?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
dsinecos
  • 571
  • 6
  • 13

4 Answers4

8

expect works by throwing an Error when an expectation fails.

The message property of the Error is what gets printed in the test report.

I suppose if you want to use an existing expectation and just want to augment the fail message with additional data you can catch the Error, append your additional data to the message property, then throw the Error again like this:

it('Read a note for a user', (done) => {
  request(graphqlURL)
    .post('/graphql')
    .set(baseHeaders())
    .send({
      query: graphQLQuery
    })
    .end((err, res) => {
      try {
        expect(res.status).toBe(200);
      } catch (err) {
        // --- add additional data to the error message here ---
        err.message = `${err.message}\n\nfailing query: ${graphQLQuery}`;
        throw err;  // throw the error so test fails as expected
      }
      done();
    })
});
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • This solves my problem to a large extent. There's a small hiccup though. The `console.log` statements are printed separately from the test report. The report on which tests failed is printed out separately from the corresponding `console.log` statements for that test. That makes it tedious to match the request-responses to the corresponding failed tests. Any thoughts on how to mitigate this? – dsinecos Mar 11 '19 at 09:24
  • @dsinecos I updated my answer to add the additional data to the `message` property of the `Error` so it gets printed in the test report – Brian Adams Mar 11 '19 at 21:48
  • Thanks a ton @brian-lives-outdoors, this solved my problem – dsinecos Mar 12 '19 at 09:11
2

You could create a custom matcher that logs the response when an expectation fails:

expect.extend({
  statusCode(expected, response) {
    const { status } = response
    const pass = expected === status

    if (pass) {
      return {
        message: () =>
          `expected ${status} to be ${expected}`,
        pass: true
      }
    } else {
      return {
        message: () =>
          `expected ${status} to be ${expected}. Response: ${response}`,
        pass: false
      }
    }
  }
})

Then in your test, use the custom matcher instead of the typical expect:

it('Read a note for a user', (done) => {
  request(graphqlURL)
    .post('/graphql')
    .set(baseHeaders())
    .send({
      query: graphQLQuery
    })
    .end((err, res) => {
      expect.statusCode(200, res)     
      done()
    })
})

Unfortunately, there isn't really a way to access the HTTP request using supertest. But you could add any arbitrary information about the request that you do have to the signature of your custom matcher.

djfdev
  • 5,747
  • 3
  • 19
  • 38
  • Thanks, this is one of the approaches, I considered. The drawback I can see is that I'll have to write custom matchers for different kinds of comparison. You shared a matcher for status code. In the tests I'd also be comparing different parts of the response body. As I understand I'd have to write a custom matcher for each? Does this make sense? – dsinecos Mar 11 '19 at 09:28
  • You could write a similar custom matcher with logging that compares the supertest response to another object and verifies the object is a subset of the response. So something similar to `expect.toMatchObject`, however with the modified message. – djfdev Mar 11 '19 at 09:38
  • I've accepted another answer. My concern is that this would either limit the number of matchers I can use on my response or I'll end up rewriting the matchers provided by Jest. Thanks for the help :) – dsinecos Mar 12 '19 at 09:12
0

If you add additional expect for the body, it would be logged if it does not match the expected one. Having this expect before the status code expect will result in the desired by you effect.

-1

You can print response like this :

it('Read a note for a user', (done) => {
  request(graphqlURL)
    .post('/graphql')
    .set(baseHeaders())
    .send({
      query: graphQLQuery
    })
    .end((err, res) => {
      if(!expect(res.status).toBe(200)) console.log('res',res) // print response
      expect(res.status).toBe(200);          
      done();
    })

  });