res.body is empty in this test that uses supertest and Node.js

blundin picture blundin · Oct 17, 2014 · Viewed 18.1k times · Source

I am testing a Node.js API with supertest, and I cannot explain why the res.body object superset returns is empty. The data shows up in the res.text object, but not res.body, any idea how to fix this?

I am using Express and body-parser:

app.use(bodyParser.json());
app.use(bodyParser.json({ type: jsonMimeType }));
app.use(bodyParser.urlencoded({ extended: true }));

Here is the API method I am testing:

app.get(apiPath + '/menu', function(req, res) {
  var expiration = getExpiration();

  res.set({
    'Content-Type': jsonMimeType,
    'Content-Length': jsonTestData.length,
    'Last-Modified': new Date(),
    'Expires': expiration,
    'ETag': null
  });

  res.json({ items: jsonTestData });
}

Here are the tests I am executing against this API method:

describe('GET /menu', function() {
  describe('HTTP headers', function() {
    it('responds with the right MIME type', function(done) {
      request(app)
        .get(apiPath + '/menu')
        .set('Accept', 'application/vnd.burgers.api+json')
        .expect('Content-Type', 'application/vnd.burgers.api+json; charset=utf-8')
        .expect(200, done);
    });

    it('responds with the right expiration date', function(done) {
      var tomorrow = new Date();
      tomorrow.setDate(tomorrow.getDate() + 1);
      tomorrow.setHours(0,0,0,0);

      request(app)
        .get(apiPath + '/menu')
        .set('Accept', 'application/vnd.burgers.api+json; charset=utf-8')
        .expect('Expires', tomorrow.toUTCString())
        .expect(200, done);
    });

    it('responds with menu items', function(done) {
      request(app)
        .get(apiPath + '/menu')
        .set('Accept', 'application/vnd.burgers.api+json; charset=utf-8')
        .expect(200)
        .expect(function (res) {
          console.log(res);
          res.body.items.length.should.be.above(0);
        })
        .end(done);
    });
  });
});

The failure I receive:

1) GET /menu HTTP headers responds with menu items:
     TypeError: Cannot read property 'length' of undefined
      at /Users/brian/Development/demos/burgers/menu/test/MenuApiTest.js:42:25
      at Test.assert (/Users/brian/Development/demos/burgers/menu/node_modules/supertest/lib/test.js:213:13)
      at Server.assert (/Users/brian/Development/demos/burgers/menu/node_modules/supertest/lib/test.js:132:12)
      at Server.g (events.js:180:16)
      at Server.emit (events.js:92:17)
      at net.js:1276:10
      at process._tickDomainCallback (node.js:463:13)

And finally, here is an excerpt of the result of console.log(res):

...
text: '{"items":[{"id":"1","name":"cheeseburger","price":3},{"id":"2","name":"hamburger","price":2.5},{"id":"3","name":"veggie burger","price":3},{"id":"4","name":"large fries","price":2},{"id":"5","name":"medium fries","price":1.5},{"id":"6","name":"small fries","price":1},{"id":"7","name":"large drink","price":2.5},{"id":"8","name":"medium drink","price":2},{"id":"9","name":"small drink","price":1}]}',
  body: {},
...

Answer

mattr picture mattr · Oct 28, 2014

Based on the following test you are expecting 'application/vnd.burgers.api+json; charset=utf-8' as the Content-Type:

request(app)
    .get(apiPath + '/menu')
    .set('Accept', 'application/vnd.burgers.api+json')
    .expect('Content-Type', 'application/vnd.burgers.api+json; charset=utf-8')
    .expect(200, done);

This express route also shows you setting the header to some custom value, jsonMimeType:

app.get(apiPath + '/menu', function(req, res) {
  var expiration = getExpiration();

  res.set({
    'Content-Type': jsonMimeType,
    'Content-Length': jsonTestData.length,
    'Last-Modified': new Date(),
    'Expires': expiration,
    'ETag': null
  });

  res.json({ items: jsonTestData });
}

If this is the case, supertest isnt going to parse that JSON automatically for you. The content-type header must start with the string 'application/json'. If you cant make that happen, then you will have to use the JSON.parse function yourself to convert that text string to an object.

supertest uses this file to determine if you are sending json or not. Under the hood, supertest actually starts up your express server, makes the one request via HTTP, and quickly shuts it down. After that HTTP handoff, the client side (which is basically superagent) of that HTTP request doesnt know anything about your server configuration with regard to 'application/vnd.burgers.api+json; charset=utf-8'. All it know is what its told via headers, in this case, content-type.

Also, I did try your custom header on my machine and I also got an empty body.

Edit: updated table link as stated in the comments