22

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: {},
...
blundin
  • 1,603
  • 3
  • 14
  • 29
  • 1
    I don't know that test framework but the question I have is what exactly is res when you're at console.log(res)? It looks like it's just a string representation of an array. So maybe JSON.parse(res)... – Ryan E Oct 17 '14 at 17:18
  • `res` is the response object results from the request. Per the [documentation](https://github.com/visionmedia/supertest) supertest just passes the response object through to the callback function. – blundin Oct 17 '14 at 17:46
  • You're serializing the response with res.json() right? And your api for the expect function states that it expects a string, regex, or parsed body object https://github.com/visionmedia/supertest#expectbody-fn – Ryan E Oct 17 '14 at 18:05
  • 1
    Yes, I am serializing the response. But the `expect(body, [fn])` that you referenced is not the method I am using. I use `.expect(function(res) {})`. The documentation for this method states: "Pass a custom assertion function. It'll be given the response object to check. If the response is ok, it should return falsy, most commonly by not returning anything. If the check fails, throw an error or return a truthy value like a string that'll be turned into an error." [An example can be found here](https://github.com/visionmedia/supertest#expectfunctionres-). – blundin Oct 17 '14 at 20:54

5 Answers5

7

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

mattr
  • 5,458
  • 2
  • 27
  • 22
  • Thanks @mattyice! That's exactly it, I'm using a custom content type for the API, and parsing the JSON worked. – blundin Oct 30 '14 at 14:38
  • The table you linked to no longer exists, as `node-mime` has switched to using `mime-db` for its mime type data. You can find the JSON data [here](https://github.com/jshttp/mime-db/blob/master/db.json). – Nick McCurdy Mar 29 '15 at 19:51
6

This is old, but it helped me so thought I might share some knowledge.

Working off of mattr example, I found that that information was actually in the res.text, not the res.body.

I ended up adding some special handling for:

if(res.headers['content-type'] == 'myUniqueContentType' && res.body===undefined){ 
    res.body = JSON.parse(res.text);
}
TheOneWhoPrograms
  • 629
  • 1
  • 8
  • 19
0

My problem was that the .set() method set the request headers, whereas .send() will set the request body with the json data you specify.

request("/localhost:3000")
    .post("/test")
    .type("json")
    .set({color: "red"}) //this does nothing!
    .expect(200)
    .end(function(res) {
        done();
    });

The fix:

request("/localhost:3000")
    .post("/test")
    .type("json")
    .send({color: "red"}) //fixed!
    .expect(200)
    .end(function(res) {
        done();
    });
Daniel Lizik
  • 3,058
  • 2
  • 20
  • 42
0

Make Sure your supertest request has the 'accept: application/json' header set

I was noticing I only had my result as text instead of json until I set the request header to accept: application/json.

request(myServerObject)
    .get("/myJSONurl")
    .set("accept", "application/json") // This is the line you should set
    .end(function() {
       if (err) throw err
       console.log(res.body) // should have a value
    })
Cameron
  • 2,805
  • 3
  • 31
  • 45
-2

add

app.use(bodyParser.json({ type: 'application/*+json' }))

this will accept all json content types :-)

  • By I got down? if you have problem with content type using for example JSON API it is proper to set it like this. – Maciej Adamczewski Jul 25 '17 at 10:34
  • 1
    I didn't downvote, but probably because the question is about the *response* body parsed by supertest, while this change would affect the node.js server's parsing of *request* bodies. – ZachB Aug 08 '17 at 22:38