res.body is empty in this test that uses supertest

2019-04-20 01:34发布

问题:

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: {},
...

回答1:

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



回答2:

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);
}


回答3:

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();
    });


回答4:

add

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

this will accept all json content types :-)