【问题标题】:Mocha tests with async initialization code使用异步初始化代码进行 Mocha 测试
【发布时间】:2018-09-22 22:54:37
【问题描述】:

我正在为 REST 客户端库编写测试,该库必须使用 OAuth 交换“登录”服务。为了防止登录我要测试的每个端点,我想编写某种“测试设置”,但我不确定我应该如何做到这一点。

我的测试项目结构:

  • 测试
    • endpoint-category1.spec.ts
    • endpoint-category2.spec.ts

如果我只有一个“端点类别”,我会得到这样的结果:

describe('Endpoint category 1', () => {
  let api: Client = null;

  before(() => {
    api = new Client(credentials);
  });

  it('should successfully login using the test credentials', async () => {
    await api.login();
  });

  it('should return xyz\'s profile', async () => {
    const r: Lookup = await api.lookup('xyz');
    expect(r).to.be.an('object');
  });
});

我的问题:

由于 login() 方法是那里的第一个测试,它可以工作,并且客户端实例也可用于以下所有测试。但是,如何进行某种设置,使“登录的 api 实例”可用于我的其他测试文件?

【问题讨论】:

    标签: node.js unit-testing typescript mocha.js


    【解决方案1】:

    公共代码应移至beforeEach:

      beforeEach(async () => {
        await api.login();
      });
    

    此时should successfully login using the test credentials 没有多大意义,因为它没有断言任何东西。

    【讨论】:

    • 如果登录不成功会抛出异常。 api.login 方法返回一个解析为 void 的 Promise。同样的问题在这里适用:我想避免每次测试都再次登录,因为我实际上只需要登录一次即可测试所有其他端点。
    • @kentor 是的,如果 beforeEach 失败,这将失败所有套件测试,这是预期的行为。您所描述的事情应该永远在单元测试中完成,每个测试都应该是一个新的开始,独立于其他测试,因为做相反的事情会影响测试的状态并导致交叉-污染。您的测试中的 OAuth 是执行真正 XHR 请求的昂贵操作吗?
    • 是的,对于这种特定情况,oauth 交换总共包含三个请求。
    • 真正的login() 仅在对其进行测试的规范中被调用,should successfully login using the test credentials(应放入不同的describe 中以不与其他规范共享 beforeEach)。其他规范使用模拟的login,模拟的方式取决于实现,您可以直接在api 实例中模拟XHR 请求或模拟身份验证数据(真实的login() 是可以接受的,但在beforeEach 中是不可取的,因为单元测试应该很快)。这就是我希望它正确完成的方式。当然,new Client(credentials) 也应该在 beforeEach 中完成,以避免交叉污染
    • 这就是为什么我说这些是集成而不是单元测试。进行集成测试是一件好事,但它们不能替代单元测试覆盖率。我建议两者兼有——它们可以是相似的和多余的,但它们测试不同的东西。在单元测试中,您可以模拟后端。 before 每个 describe 块执行一次,所以它适合您的描述。我不建议这样做,因为在不同测试之间保持共同状态从来都不是一个好习惯,单元、集成和 e2e 都是一样的。
    【解决方案2】:
    describe('Endpoint category 1', () => {
      let api: Client = null;
    
      beforeEach(() => {
        api = new Client(credentials);
      });
    
      afterEach(() => {
        // You should make every single test to be ran in a clean environment.
        // So do some jobs here, to clean all data created by previous tests.
      });
    
      it('should successfully login using the test credentials', async () => {
        const ret = await api.login();
        // Do some assert for `ret`.
      });
    
      context('the other tests', () => {
        beforeEach(() => api.login());
        it('should return xyz\'s profile', async () => {
          const r: Lookup = await api.lookup('xyz');
          expect(r).to.be.an('object');
        });
      });
    });
    

    【讨论】:

    • api.login() 返回 Promise,但是它可能会抛出异常,因此我这样做了。在那种情况下我还需要断言吗?我还假设 beforeEach 将在该描述主体中的所有测试之前执行。您的意思是测试环境应该始终相同,因此我还应该在每次测试之前重新登录?
    • 第一个问题:是的,您需要在另一个用例中断言登录错误,因为它与“登录成功”的情况完全不同。
    • 第二个:单元测试的一个共同原则是不要将多个测试绑定在一起。这意味着您可以一次执行所有这些测试,或者每次只执行其中一个。但是怎么做?这才是重点。我们存根,我们模拟,我们创建一堆数据。然后什么?我们必须在每次测试完成后将所有这些东西重置为零,我的意思是,将系统恢复到执行每次测试之前的状态。
    • 此外,Estus Flask 是对的,您的代码完全不是单元测试,而是集成。在单元测试中,我们不使用数据库、网络或任何其他昂贵的东西。我们只是存根或模拟这些东西。更重要的是,我们将一个方法放在一个小盒子里,存根或模拟它需要的每个依赖项,控制输入,最后,我们断言输出。那是单元测试。
    【解决方案3】:

    你看过https://mochajs.org/#asynchronous-code吗?

    你可以在你的测试函数中加入一个完成参数,你会得到一个你必须调用的回调。

    done()done(error/exception)

    此操作在之前和之后也可用。

    当调用 done() mocha 知道你的异步代码已经完成。

    啊。如果你想测试登录,你不应该提供这个连接到其他测试,因为在默认配置中不能保证测试顺序。

    之后只需测试登录和注销。

    如果您需要更多关于“登录会话”的测试,请用 befores 描述一个新的测试。

    【讨论】:

    • 是的,我知道,但我正在使用 async / await 以避免此类回调:)
    猜你喜欢
    • 2014-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-03
    • 2016-04-05
    • 1970-01-01
    • 1970-01-01
    • 2020-10-27
    相关资源
    最近更新 更多