如何让 Nock 和 Mocha 一起玩?

How do I make Nock and Mocha play well together?

我正在尝试使用 nock intercept/mock 我的应用程序中的一些 HTTP 流量以进行测试。我们的应用程序向我们的另一个网站进行身份验证,我需要模拟 HTTP 200(带有 JSON 数据)和 HTTP 401(没有数据)来测试用户登录或未登录时的行为那里(分别)。

我有两个测试,单独 运行 时都可以正常工作,但是如果我 运行 整个测试套件,其中一个总是失败。我意识到 nock 是共享状态,因为它修改了 node.js 本身处理网络流量的方式,我认为这是竞争条件的原因,但我不能是唯一一个使用过两个不同 nock 拦截器的人在两个不同的测试中请求,所以我知道我遗漏了一些东西。

谁能帮我弄清楚为什么这些测试会相互影响?

我的问题与 How to retest same URL using Mocha and Nock? 有关,但我按照那里的建议做了,但没有帮助。

我的测试文件(同样,如果单独调用它们都可以正常工作,但当 运行 作为同一测试通过的一部分时会失败)如下所示:

import { expect } from 'chai';
import nock from 'nock';

import * as actionTypes from '../../src/constants/action-types';
import * as panoptes from '../../src/services/panoptes';

import { user } from '../modules/users/test-data';

const stagingHost = 'https://my-staging-server.org';

describe('Panoptes', () => {
  afterEach(function (done) {
    nock.cleanAll();
    nock.disableNetConnect();
    done();
  });

  beforeEach(function (done) {
    nock.cleanAll();
    nock.disableNetConnect();
    done();
  });

  describe('with a valid user', function (done) {
    let lastAction = null;

    const scope = nock(stagingHost)
      .get(/^\/oauth\/authorize/)
      .reply(302, '', {
        'location': 'https://localhost:3000',
        'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
        'X-Frame-Options': 'SAMEORIGIN',
        'X-XSS-Protection': '1; mode=block',
      });

    scope
      .get(/^\/api\/me/)
      .reply(200, {
        users: [user],
      });

    panoptes.checkLoginUser((action) => { lastAction = action; }).then(() => {
      nock.removeInterceptor(scope);
      done();
    });

    it('should know when somebody is logged in', function () {
      expect(lastAction).to.not.be.null;
      expect(lastAction.type).to.equal(actionTypes.SET_LOGIN_USER);
      expect(lastAction.user).to.not.be.null;
      expect(lastAction.user.id).to.equal(user.id);
      expect(lastAction.user.login).to.equal(user.login);
    });
  });
});

import { expect } from 'chai';
import nock from 'nock';

import * as actionTypes from '../../src/constants/action-types';
import * as panoptes from '../../src/services/panoptes';

const stagingHost = 'https://my-staging-server.org';

describe('Panoptes', () => {
  afterEach(function (done) {
    nock.cleanAll();
    nock.disableNetConnect();
    done();
  });

  beforeEach(function (done) {
    nock.cleanAll();
    nock.disableNetConnect();
    done();
  });

  describe('with no user', function (done) {
    let lastAction = null;

    const scope = nock(stagingHost)
      .get(/^\/oauth\/authorize/)
      .reply(302, '', {
        'Cache-Control': 'no-cache',
        'location': 'https://my-staging-server.org/users/sign_in',
        'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
        'X-Frame-Options': 'SAMEORIGIN',
        'X-XSS-Protection': '1; mode=block',
      });

    scope
      .get(/^\/api\/me/)
      .reply(401);

    panoptes.checkLoginUser((action) => { lastAction = action; }).then(() => {
      nock.removeInterceptor(scope);
      done();
    });

    it('should know that nobody is logged in', function () {
      expect(lastAction).to.not.be.null;
      expect(lastAction.type).to.equal(actionTypes.SET_LOGIN_USER);
      expect(lastAction.user).to.be.null;
    });
  });
});

我觉得问题不在nock,而是你的mocha hook的执行顺序:

举个例子:

describe('Panoptes', () => {

  afterEach(function () {
    console.log('ORDER: after each');
  });

  beforeEach(function () {
    console.log('ORDER: before each');
  });

  describe('with a valid user', function () {

    console.log('ORDER: with a valid user');

    it('should know when somebody is logged in', function () {
      console.log('ORDER: should know when somebody is logged in');
    });

  });

  describe('with no user', function () {

    console.log('ORDER: with no user');

    it('should know that nobody is logged in', function () {
      console.log('ORDER: should know that nobody is logged in');
    });

  });

});

当我们 运行 时,我们得到以下输出顺序:

ORDER: with a valid user
ORDER: with no user
ORDER: before each
ORDER: should know when somebody is logged in
ORDER: after each
ORDER: before each
ORDER: should know that nobody is logged in
ORDER: after each

afterEach/beforeEach 运行s 在每个 it 之前和之后,但是 describe 主体在调用这些钩子之前得到评估。您应该将每个箭尾都包裹在 before 中。 (另外 describe 不使用 done 参数)

像这样的东西应该可以工作:

describe('with no user', function () {

  before(function() {
    const scope = nock(stagingHost)
      .get(/^\/oauth\/authorize/)
      .reply(302, '', {
        'Cache-Control': 'no-cache',
        'location': 'https://my-staging-server.org/users/sign_in',
        'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
        'X-Frame-Options': 'SAMEORIGIN',
        'X-XSS-Protection': '1; mode=block',
      });

    scope
      .get(/^\/api\/me/)
      .reply(401);
  });


  it('should know that nobody is logged in', function (done) {
    panoptes.checkLoginUser((action) => {
      expect(action).to.not.be.null;
      expect(action.type).to.equal(actionTypes.SET_LOGIN_USER);
      expect(action.user).to.be.null;
      done();
    });
  });

});

我使用了 sanketh-katta 的答案,我将其归功于解决,但稍微修改了 it 块,所以为了完整性,我包括了我的代码:

it('should know when somebody is logged in', function(done) {
  panoptes.checkLoginUser((action) => {
    try {
      expect(action).to.not.be.null;
      expect(action.type).to.equal(actionTypes.SET_LOGIN_USER);
      expect(action.user).to.not.be.null;
      expect(action.user.id).to.equal(user.id);
      expect(action.user.login).to.equal(user.login);
      done();
    } catch (ex) {
      done(ex);
    }
  });
});

以前,当其中一个测试失败时,done() 调用将永远无法到达,因此我会收到测试已超时的消息,而不是特定的失败。