为什么supertest(测试表达)return状态是301而不是200?

Why does supertest (testing express) return a status of 301 and not 200?

我有一个基于 express 的 restful 应用程序,我决定使用 supertest 对其进行测试。这是我第一次使用 supertest 进行测试,我很困惑。其中一条路线是:

/api/version

哪个returns版本:

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

效果很好,当我查看 Chrome 中的“http://localhost:9000/api/version/”时,我看到:

{"version":"v0.00.000-38-gf395113 & textAngular: v1.5.16"}

这是我所期望的。状态为 200(或 304,如果它已被 Chrome 缓存),如预期的那样。

但是,当我使用 mocha 和 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();
        });
     });
});

我明白了:

Error: expected 200 "OK", got 301 "Moved Permanently"

除非我注释掉expect(200) line。此外,令我惊讶的是我没有看到 'application/json' 的 'Content-Type'。也就是我上面设置的。

我很想知道为什么会这样?

应用程序有点大,我可以报告更多详细信息,但我怀疑有人理解为什么会出现 304 状态。

如果我注释掉 expect(200) 行并捕获我看到的响应:

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)

我注意到的地方:text: 'Redirecting to <a href="/api/version/">/api/version/</a>\n' 这提供了线索。 非常感谢您的帮助。

我遇到了同样的问题。如果这对您来说是同一个问题,您只需更改

.get('/api/version')

.get('/api/version/')

尾随的正斜杠让测试变得与众不同。