Supertest: Body is empty

Created on 16 Jan 2014  路  14Comments  路  Source: visionmedia/supertest

Hi,There
I did some testing on my demo app, find some strange result.
My demo app has a healthcheck, it returns with a 'ack' in the response (suppose 'ack' should be in response's body). code fragment shows below
app.get('/demoapp/healthcheck', function (request, response) {
response.writeHead(200, {'Content-Type': 'text/plain'});
response.end('ack');
});

Then my test code shows below
var should = require('should');
var assert = require('assert');
var request = require('supertest');

describe('Routing', function() {
var url = 'http://localhost:8080';

describe('demoservice', function() {
it('do health check', function(done) {

request(url)
.get('/demoapp/healthcheck')    
.expect(200,function(){
    console.log('expect status');
}) //Status code
.end(function(err, res) {
      if (err) {
        throw err;
      }     
      console.log(res);
      console.log(res.text);
      console.log(res.body);    
      res.text.should.have.match(/^ack/);
      done();
    });
});

});
});

In the console, output is so weird,
res shows it's 'body' is {}, but it has 'text' 'ackack' (not typo,it's real two ack), is there anybody can tell me why? Thanks in advance.

bug

All 14 comments

whole res shows in the console:

{ req:
{ domain: null,
_events: { drain: [Function], error: [Function], response: [Object] },
_maxListeners: 10,
output: [],
outputEncodings: [],
writable: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_headerSent: true,
_header: 'GET /demoapp/healthcheck HTTP/1.1\r\nHost: localhost:10060\r\nAccept-Encoding: gzip, deflate\r\nCookie: \r\nConnection: close\r\n\r\n',
_hasBody: true,
_trailer: '',
finished: true,
_hangupClose: false,
socket:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Circular],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
connection:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Circular],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
agent: false,
socketPath: undefined,
method: 'GET',
path: '/demoapp/healthcheck',
_headers:
{ host: 'localhost:10060',
'accept-encoding': 'gzip, deflate',
cookie: '' },
_headerNames:
{ host: 'Host',
'accept-encoding': 'Accept-Encoding',
cookie: 'Cookie' },
parser: null,
res:
{ _readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
socket: [Object],
connection: [Object],
httpVersion: '1.1',
complete: true,
headers: [Object],
trailers: {},
_pendings: [],
_pendingIndex: 0,
url: '',
method: null,
statusCode: 200,
client: [Object],
_consuming: true,
_dumped: false,
httpVersionMajor: 1,
httpVersionMinor: 1,
upgrade: false,
req: [Circular],
text: 'ackack',
pipe: [Function],
addListener: [Function],
on: [Function],
pause: [Function],
resume: [Function],
read: [Function],
body: undefined } },
res:
{ _readableState:
{ highWaterMark: 16384,
buffer: [],
length: 0,
pipes: null,
pipesCount: 0,
flowing: false,
ended: true,
endEmitted: true,
reading: false,
calledRead: true,
sync: false,
needReadable: true,
emittedReadable: false,
readableListening: false,
objectMode: false,
defaultEncoding: 'utf8',
ranOut: false,
awaitDrain: 0,
readingMore: false,
decoder: [Object],
encoding: 'utf8' },
readable: false,
domain: null,
_events:
{ end: [Object],
data: [Object],
readable: [Function],
close: [Function],
error: [Function] },
_maxListeners: 10,
socket:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Object],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
connection:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Object],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
httpVersion: '1.1',
complete: true,
headers:
{ 'x-powered-by': 'Express',
'content-type': 'text/plain',
date: 'Thu, 16 Jan 2014 01:56:46 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
trailers: {},
_pendings: [],
_pendingIndex: 0,
url: '',
method: null,
statusCode: 200,
client:
{ _connecting: false,
_handle: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_maxListeners: 10,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
onend: null,
destroyed: true,
errorEmitted: false,
bytesRead: 165,
_bytesDispatched: 130,
_pendingData: null,
_pendingEncoding: '',
parser: null,
_httpMessage: [Object],
ondata: null,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
_consuming: true,
_dumped: false,
httpVersionMajor: 1,
httpVersionMinor: 1,
upgrade: false,
req:
{ domain: null,
_events: [Object],
_maxListeners: 10,
output: [],
outputEncodings: [],
writable: true,
_last: true,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_headerSent: true,
_header: 'GET /demoapp/healthcheck HTTP/1.1\r\nHost: localhost:10060\r\nAccept-Encoding: gzip, deflate\r\nCookie: \r\nConnection: close\r\n\r\n',
_hasBody: true,
_trailer: '',
finished: true,
_hangupClose: false,
socket: [Object],
connection: [Object],
agent: false,
socketPath: undefined,
method: 'GET',
path: '/demoapp/healthcheck',
_headers: [Object],
_headerNames: [Object],
parser: null,
res: [Circular] },
text: 'ackack',
pipe: [Function],
addListener: [Function],
on: [Function],
pause: [Function],
resume: [Function],
read: [Function],
body: undefined },
links: {},
text: 'ackack',
body: {},
files: {},
buffered: true,
headers:
{ 'x-powered-by': 'Express',
'content-type': 'text/plain',
date: 'Thu, 16 Jan 2014 01:56:46 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
header:
{ 'x-powered-by': 'Express',
'content-type': 'text/plain',
date: 'Thu, 16 Jan 2014 01:56:46 GMT',
connection: 'close',
'transfer-encoding': 'chunked' },
statusCode: 200,
status: 200,
statusType: 2,
info: false,
ok: true,
redirect: false,
clientError: false,
serverError: false,
error: false,
accepted: false,
noContent: false,
badRequest: false,
unauthorized: false,
notAcceptable: false,
forbidden: false,
notFound: false,
type: 'text/plain',
setEncoding: [Function],
redirects: [] }

I'm having the same issue, did you figure out why it's occuring? Perhaps it's a bug.

cant read any of the code in here, please format stuff otherwise it's just noise

same issue

Looks like a bug when you provide a callback to expect() and also .end(). It ends up calling .end() internally twice, which is what creates the "ackack" instead of just "ack".

I suggest don't provide a callback to .end() for now.

I'm experiencing a similar issue although I'm not sure exactly the same cause...

I'm using express compress() middleware, and for responses above a threshold, the response is gzipped and chunked (so no Content-Length header is provided.)

When this occurs, superagent doesn't seem to buffer the response even though I'm using .buffer():

describe('GET /stats', () => {
  it('should return os stats', done => {
    request(app)
      .get('/stats')
      //.set('Accept-Encoding', null) // otherwise we get chunked encoding
      .set('Accept', 'application/json')
      .buffer(true)
      .expect('Content-Type', /json/)
      .expect(res => {
        expect(res.text.length).to.be.greaterThan(0);
        expect(res.body.hostname).to.be.a('string');
      })
      .expect(200,done);
  })
});

Note that if I uncomment the Accept-Encoding line above, it prevents the client from providing that header, the server doesn't gzip/chunk the response and the test passes just fine.

I find it odd that superagent can't deal with something as common as Transfer-Encoding: chunked - hopefully I'm doing something wrong - but I can't figure out what, or figure out how to force the response body to be buffered and parsed.

Response object (with some redundant data removed)

{
  domain: null,
  _events: {},
  _eventsCount: 0,
  _maxListeners: undefined,
  res: 
   IncomingMessage {
     _readableState: 
      ReadableState {
        objectMode: false,
        highWaterMark: 16384,
        buffer: [Object],
        length: 0,
        pipes: null,
        pipesCount: 0,
        flowing: false,
        ended: true,
        endEmitted: true,
        reading: false,
        sync: false,
        needReadable: false,
        emittedReadable: false,
        readableListening: false,
        resumeScheduled: false,
        defaultEncoding: 'utf8',
        ranOut: false,
        awaitDrain: 0,
        readingMore: false,
        decoder: null,
        encoding: null },
     readable: false,
     domain: null,
     _events: { end: [Object], error: [Object], close: [Function: bound emit] },
     _eventsCount: 3,
     _maxListeners: undefined,
     socket: 
      Socket { ... },
     connection: 
      Socket {
        connecting: false,
        _hadError: false,
        _handle: null,
        _parent: null,
        _host: null,
        _readableState: [Object],
        readable: false,
        domain: null,
        _events: [Object],
        _eventsCount: 9,
        _maxListeners: undefined,
        _writableState: [Object],
        writable: false,
        allowHalfOpen: false,
        destroyed: true,
        _bytesDispatched: 158,
        _sockname: null,
        _pendingData: null,
        _pendingEncoding: '',
        server: null,
        _server: null,
        parser: null,
        _httpMessage: [Object],
        read: [Function],
        _consuming: true,
        write: [Function: writeAfterFIN],
        _idleNext: null,
        _idlePrev: null,
        _idleTimeout: -1 },
     httpVersionMajor: 1,
     httpVersionMinor: 1,
     httpVersion: '1.1',
     complete: false,
     headers: 
      { 'x-powered-by': 'Express',
        'x-content-type-options': 'nosniff',
        'x-xss-protection': '1; mode=block',
        'x-frame-options': 'SAMEORIGIN',
        'access-control-allow-origin': '*',
        'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept',
        'access-control-allow-credentials': 'true',
        'content-type': 'application/json; charset=utf-8',
        etag: 'W/"51c-Gen7IbIahZKE/zqzs2CBRw"',
        'set-cookie': [Object],
        vary: 'Accept-Encoding',
        'content-encoding': 'gzip',
        date: 'Wed, 19 Oct 2016 15:30:09 GMT',
        connection: 'close',
        'transfer-encoding': 'chunked' },
     rawHeaders: [ ... ],
     trailers: {},
     rawTrailers: [],
     upgrade: false,
     url: '',
     method: null,
     statusCode: 200,
     statusMessage: 'OK',
     client: 
      Socket { ... },
     _consuming: true,
     _dumped: false,
     req: 
      ClientRequest {
        domain: null,
        _events: [Object],
        _eventsCount: 4,
        _maxListeners: undefined,
        output: [],
        outputEncodings: [],
        outputCallbacks: [],
        outputSize: 0,
        writable: true,
        _last: true,
        chunkedEncoding: false,
        shouldKeepAlive: false,
        useChunkedEncodingByDefault: false,
        sendDate: false,
        _removedHeader: {},
        _contentLength: 0,
        _hasBody: true,
        _trailer: '',
        finished: true,
        _headerSent: true,
        socket: [Object],
        connection: [Object],
        _header: 'GET /stats HTTP/1.1\r\nHost: 127.0.0.1:52307\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: node-superagent/2.3.0\r\nAccept: application/json\r\nConnection: close\r\n\r\n',
        _headers: [Object],
        _headerNames: [Object],
        _onPendingData: null,
        agent: [Object],
        socketPath: undefined,
        method: 'GET',
        path: '/stats',
        _ended: true,
        parser: null,
        res: [Circular] },
     setEncoding: [Function],
     on: [Function],
     text: '',
     read: [Function],
     body: '' },
  links: {},
  text: '',
  body: '',
  files: undefined,
  buffered: true,
  headers: { ... },
  header: 
   { 'x-powered-by': 'Express',
     'x-content-type-options': 'nosniff',
     'x-xss-protection': '1; mode=block',
     'x-frame-options': 'SAMEORIGIN',
     'access-control-allow-origin': '*',
     'access-control-allow-headers': 'Origin, X-Requested-With, Content-Type, Accept',
     'access-control-allow-credentials': 'true',
     'content-type': 'application/json; charset=utf-8',
     etag: 'W/"51c-Gen7IbIahZKE/zqzs2CBRw"',
     'set-cookie': [ 'connect.sid=s%3AlMI1wtXBJFjBtVmi-_OcrepHKaCszRxS.QOHfNGUyD06bDfCxQPHHG%2FvMmvwoTX2%2Bq%2FmpWYOCb5c; Path=/; HttpOnly' ],
     vary: 'Accept-Encoding',
     'content-encoding': 'gzip',
     date: 'Wed, 19 Oct 2016 15:30:09 GMT',
     connection: 'close',
     'transfer-encoding': 'chunked' },
  statusCode: 200,
  status: 200,
  statusType: 2,
  info: false,
  ok: true,
  redirect: false,
  clientError: false,
  serverError: false,
  error: false,
  accepted: false,
  noContent: false,
  badRequest: false,
  unauthorized: false,
  notAcceptable: false,
  forbidden: false,
  notFound: false,
  charset: 'utf-8',
  type: 'application/json',
  setEncoding: [Function: bound ],
  redirects: [] }

+1

see #348 for a similar issue

+1

+1

+1

+1

+1

In my case it turned out that I didn't set the Content-Type header in responses.

Previously I was calling res.send(object), so the header was set to application/json automatically, but after changing the code to use res.write(...) and res.end(...) I had to set it myself. After that, superagent would read the whole chunked response correctly.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nitrocode picture nitrocode  路  4Comments

nazreen picture nazreen  路  3Comments

bookercodes picture bookercodes  路  5Comments

nareshbhatia picture nareshbhatia  路  6Comments

mayacr86 picture mayacr86  路  4Comments