I have an express based restful app, which I decided to test using supertest. This is my first testing with supertest and I am confused. One of the routes is:
/api/version
Which returns the version:
export function index(req: express.Request, res: express.Response) {
log('index of version.controller');
let ver = { version: version.getVersion() + ' & textAngular: v' + textAngularVersion.getTextAngularVersion() };
res.setHeader('Content-Type', 'application/json');
return res.status(200).json(ver);
}
Which works fine and when I look in Chrome at 'http://localhost:9000/api/version/' I see:
{"version":"v0.00.000-38-gf395113 & textAngular: v1.5.16"}
which is what I expect. And the status is either 200 (or 304 if it has been cached by Chrome) as expected.
HOWEVER, When I test with mocha and supertest:
describe('GET /api/version', () => {
it('should respond with version', (done: any) => {
request(app.server)
.get('/api/version')
.expect('Content-Type', 'text/html; charset=UTF-8')
.expect(200)
.end((err: any, res: any) => {
if (err) {
return done(err);
}
done();
});
});
});
I see:
Error: expected 200 "OK", got 301 "Moved Permanently"
Unless I comment out the expect(200) line
. Also I was surprised that I didn't see a 'Content-Type' of 'application/json'. Which is what I set above.
I would love to understand why this is happening?
The application is a bit large, and I can report more details, but I suspect that someone understands why a status of 304 shows up.
If I comment out the expect(200) line and capture the response I see:
2017-02-26T14:51:08-0700 <log> app.js:124 (Server.<anonymous>) Express server listening on 9000, in test mode
GET /api/version
::ffff:127.0.0.1 - GET /api/version HTTP/1.1 301 57 - 6.867 ms
Response {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
res:
IncomingMessage {
_readableState:
ReadableState {
objectMode: false,
highWaterMark: 16384,
buffer: [Object],
length: 0,
pipes: null,
pipesCount: 0,
flowing: true,
ended: true,
endEmitted: true,
reading: false,
sync: true,
needReadable: false,
emittedReadable: false,
readableListening: false,
resumeScheduled: false,
defaultEncoding: 'utf8',
ranOut: false,
awaitDrain: 0,
readingMore: false,
decoder: [Object],
encoding: 'utf8' },
readable: false,
domain: null,
_events:
{ end: [Object],
data: [Object],
error: [Object],
close: [Function: bound emit] },
_eventsCount: 4,
_maxListeners: undefined,
socket:
Socket {
connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
_bytesDispatched: 137,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Object],
read: [Function],
_consuming: true,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
connection:
Socket {
connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
_bytesDispatched: 137,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Object],
read: [Function],
_consuming: true,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers:
{ 'x-powered-by': 'Express',
'content-type': 'text/html; charset=UTF-8',
'content-length': '57',
'x-content-type-options': 'nosniff',
location: '/api/version/',
date: 'Sun, 26 Feb 2017 21:51:08 GMT',
connection: 'close' },
rawHeaders:
[ 'X-Powered-By',
'Express',
'Content-Type',
'text/html; charset=UTF-8',
'Content-Length',
'57',
'X-Content-Type-Options',
'nosniff',
'Location',
'/api/version/',
'Date',
'Sun, 26 Feb 2017 21:51:08 GMT',
'Connection',
'close' ],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 301,
statusMessage: 'Moved Permanently',
client:
Socket {
connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
_bytesDispatched: 137,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Object],
read: [Function],
_consuming: true,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
_consuming: true,
_dumped: false,
req:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 3,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedHeader: {},
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'GET /api/version HTTP/1.1\r\nHost: 127.0.0.1:9000\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: node-superagent/3.5.0\r\nConnection: close\r\n\r\n',
_headers: [Object],
_headerNames: [Object],
_onPendingData: null,
agent: [Object],
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/api/version',
_ended: true,
res: [Circular],
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null },
text: 'Redirecting to <a href="/api/version/">/api/version/</a>\n',
read: [Function] },
request:
Test {
domain: null,
_events: {},
_eventsCount: 0,
_maxListeners: undefined,
_agent: false,
_formData: null,
method: 'GET',
url: 'http://127.0.0.1:9000/api/version',
_header: { 'user-agent': 'node-superagent/3.5.0' },
header: { 'User-Agent': 'node-superagent/3.5.0' },
writable: true,
_redirects: 1,
_maxRedirects: 0,
cookies: '',
qs: {},
qsRaw: [],
_redirectList: [],
_streamRequest: false,
_buffer: true,
app:
Server {
domain: null,
_events: [Object],
_eventsCount: 4,
_maxListeners: undefined,
_connections: 0,
_handle: [Object],
_usingSlaves: false,
_slaves: [],
_unref: false,
allowHalfOpen: true,
pauseOnConnect: false,
httpAllowHalfOpen: false,
timeout: 120000,
_pendingResponseData: 0,
maxHeadersCount: null,
_connectionKey: '6::::9000' },
_asserts: [ [Function: bound ] ],
req:
ClientRequest {
domain: null,
_events: [Object],
_eventsCount: 3,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedHeader: {},
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket: [Object],
connection: [Object],
_header: 'GET /api/version HTTP/1.1\r\nHost: 127.0.0.1:9000\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: node-superagent/3.5.0\r\nConnection: close\r\n\r\n',
_headers: [Object],
_headerNames: [Object],
_onPendingData: null,
agent: [Object],
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/api/version',
_ended: true,
res: [Object],
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null },
protocol: 'http:',
host: '127.0.0.1:9000',
_endCalled: true,
_callback: [Function],
res:
IncomingMessage {
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 4,
_maxListeners: undefined,
socket: [Object],
connection: [Object],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Object],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 301,
statusMessage: 'Moved Permanently',
client: [Object],
_consuming: true,
_dumped: false,
req: [Object],
text: 'Redirecting to <a href="/api/version/">/api/version/</a>\n',
read: [Function] },
response: [Circular],
called: true },
req:
ClientRequest {
domain: null,
_events:
{ drain: [Object],
error: [Object],
prefinish: [Function: requestOnPrefinish] },
_eventsCount: 3,
_maxListeners: undefined,
output: [],
outputEncodings: [],
outputCallbacks: [],
outputSize: 0,
writable: true,
_last: true,
upgrading: false,
chunkedEncoding: false,
shouldKeepAlive: false,
useChunkedEncodingByDefault: false,
sendDate: false,
_removedHeader: {},
_contentLength: 0,
_hasBody: true,
_trailer: '',
finished: true,
_headerSent: true,
socket:
Socket {
connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
_bytesDispatched: 137,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular],
read: [Function],
_consuming: true,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
connection:
Socket {
connecting: false,
_hadError: false,
_handle: null,
_parent: null,
_host: null,
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 8,
_maxListeners: undefined,
_writableState: [Object],
writable: false,
allowHalfOpen: false,
destroyed: true,
_bytesDispatched: 137,
_sockname: null,
_pendingData: null,
_pendingEncoding: '',
server: null,
_server: null,
parser: null,
_httpMessage: [Circular],
read: [Function],
_consuming: true,
_idleNext: null,
_idlePrev: null,
_idleTimeout: -1 },
_header: 'GET /api/version HTTP/1.1\r\nHost: 127.0.0.1:9000\r\nAccept-Encoding: gzip, deflate\r\nUser-Agent: node-superagent/3.5.0\r\nConnection: close\r\n\r\n',
_headers:
{ host: '127.0.0.1:9000',
'accept-encoding': 'gzip, deflate',
'user-agent': 'node-superagent/3.5.0' },
_headerNames:
{ host: 'Host',
'accept-encoding': 'Accept-Encoding',
'user-agent': 'User-Agent' },
_onPendingData: null,
agent:
Agent {
domain: null,
_events: [Object],
_eventsCount: 1,
_maxListeners: undefined,
defaultPort: 80,
protocol: 'http:',
options: [Object],
requests: {},
sockets: [Object],
freeSockets: {},
keepAliveMsecs: 1000,
keepAlive: false,
maxSockets: Infinity,
maxFreeSockets: 256 },
socketPath: undefined,
timeout: undefined,
method: 'GET',
path: '/api/version',
_ended: true,
res:
IncomingMessage {
_readableState: [Object],
readable: false,
domain: null,
_events: [Object],
_eventsCount: 4,
_maxListeners: undefined,
socket: [Object],
connection: [Object],
httpVersionMajor: 1,
httpVersionMinor: 1,
httpVersion: '1.1',
complete: true,
headers: [Object],
rawHeaders: [Object],
trailers: {},
rawTrailers: [],
upgrade: false,
url: '',
method: null,
statusCode: 301,
statusMessage: 'Moved Permanently',
client: [Object],
_consuming: true,
_dumped: false,
req: [Circular],
text: 'Redirecting to <a href="/api/version/">/api/version/</a>\n',
read: [Function] },
aborted: undefined,
timeoutCb: null,
upgradeOrConnect: false,
parser: null,
maxHeadersCount: null },
text: 'Redirecting to <a href="/api/version/">/api/version/</a>\n',
body: {},
files: undefined,
buffered: true,
headers:
{ 'x-powered-by': 'Express',
'content-type': 'text/html; charset=UTF-8',
'content-length': '57',
'x-content-type-options': 'nosniff',
location: '/api/version/',
date: 'Sun, 26 Feb 2017 21:51:08 GMT',
connection: 'close' },
header:
{ 'x-powered-by': 'Express',
'content-type': 'text/html; charset=UTF-8',
'content-length': '57',
'x-content-type-options': 'nosniff',
location: '/api/version/',
date: 'Sun, 26 Feb 2017 21:51:08 GMT',
connection: 'close' },
statusCode: 301,
status: 301,
statusType: 3,
info: false,
ok: false,
redirect: true,
clientError: false,
serverError: false,
error: false,
accepted: false,
noContent: false,
badRequest: false,
unauthorized: false,
notAcceptable: false,
forbidden: false,
notFound: false,
type: 'text/html',
charset: 'UTF-8',
links: {},
setEncoding: [Function: bound ],
redirects: [] }
✓ should respond with version (107ms)
Where I notice: text: 'Redirecting to <a href="/api/version/">/api/version/</a>\n'
which gives a clue.
You help is greatly appreciated.
With SuperTest there’s no need to verify your API by hand. Plus, using it gives you virtually free integration tests. Now you can test and code new features at the same time.
From here you can get creative, and test express routers and middleware (with a little bootstrapping), and remember to visit the superagent documentation, because you can use those methods with supertest (supertest is based on superagent). The full code is located in the testing-express-api repository.
From this, you’ll be able to move into storing cookies or tokens to easily switch between sessions and test data access per user! SuperTest can be used with any server available on your local network (or the Internet), but it also has a super power: giving it an Express server directly.
I had the same issue. If it is the same issue for you, you just need to change
.get('/api/version')
to
.get('/api/version/')
That trailing forward slash makes a world of difference for the test.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With