【问题标题】:chai-as-promised tests don't work with $q promiseschai-as-promised 测试不适用于 $q 承诺
【发布时间】:2016-04-18 22:00:14
【问题描述】:

我正在尝试让 chai-as-promised$q 承诺与业力单元测试一起工作。

  svc.test = function(foo){
    if (!foo){
      // return Promise.reject(new Error('foo is required'));
      return $q.reject(new Error('foo is required'));
    } else {
      // get data via ajax here
      return $q.resolve({});
    }
  };


  it.only('should error on no foo', function(){
    var resolvedValue = MyServices.test();
    $rootScope.$apply();
    return resolvedValue.should.eventually.be.rejectedWith(TypeError, 'foo is required');
  });

单元测试只是超时。我不确定我在这里做错了什么才能得到正确解决的承诺。使用$q 似乎是个问题——当我使用本机Promise.reject() 时它工作正常。

我在这里提交了一张票,但似乎没有人回应: https://github.com/domenic/chai-as-promised/issues/150

【问题讨论】:

    标签: angularjs karma-runner chai chai-as-promised


    【解决方案1】:

    chai-as-promised 期望修改 Promise 断言的方式是 transferPromiseness method

    默认情况下,Chai 作为 Promised 的断言返回的承诺是 常规 Chai 断言对象,使用单个 then 方法扩展 源自输入承诺。例如,要改变这种行为 用更有用的糖方法输出一个承诺,例如发现 在大多数承诺库中,您可以覆盖 chaiAsPromised.transferPromiseness.

    对于 Angular 1.3+ 支持,$q 承诺可以通过 $$state 属性进行鸭式类型,因此本机承诺不会受到影响:

    chaiAsPromised.transferPromiseness = function (assertion, promise) {
      assertion.then = promise.then.bind(promise);
    
      if (!('$$state' in promise))
        return;
    
      inject(function ($rootScope) {
        if (!$rootScope.$$phase)
          $rootScope.$digest();
      });
    };
    

    chaiAsPromisedthen 链接每个断言的承诺。即使承诺已解决,链的其余部分仍需要使用 $rootScope.$digest() 手动触发摘要。

    只要spec不包含异步代码,就变成同步的,不需要返回promise:

    it('...', () => {
      ...
      expect(...).to.eventually...;
      expect(...).to.eventually...;
    });
    

    transferPromiseness 未设置时,在每组eventually 断言/预期之后等于强制$rootScope.$digest()

    it('...', () => {
      ...
      expect(...).to.eventually...;
      expect(...).to.eventually...;
      $rootScope.$digest();
    });
    

    【讨论】:

    • 似乎应该由插件提供一些东西,因为 angular/$q 非常常见,大多数公司都使用 chai。
    • @chovy 当然,为什么不呢,可以为 chai-as-promised 创建一个包装包,用列出的代码重新导出它(幸运的是,它甚至不必为此分叉)。我已经习惯了 Mocha/Chai 包需要任何方式的安装文件的想法,在那里多出几行对我来说不是问题。
    • @Willa 在var chaiAsPromised = require("chai-as-promised") 之后。如果规范在 Karma 或浏览器环境中运行,则应将其放置在所有规范之前加载的脚本中。
    • 谢谢!抱歉,虽然我没有提到我正在使用 grunt 运行业力。所以我只有一个业力配置文件。准确地说是因果报应摩卡和柴
    • @Willa 你可能在 Karma 配置中有 files: ['test/setup.js', 'test/*.spec.js'] 之类的东西。它们按照指定的顺序运行,第一个是上面的代码应该去的地方。
    【解决方案2】:

    您需要更改测试中的执行顺序。具有 chai-as-promised 的异步任务需要在预期之前发生

    it('does not work', () => {
      $timeout.flush();
      expect(myAsyncTask()).to.eventually.become('foo');
    })
    
    it('does work', () => {
      expect(myAsyncTask()).to.eventually.become('foo');
      $timeout.flush();      
    })
    

    您需要在刷新异步任务队列之前启动对异步任务的调用。

    另外,不要使用$rootScope.$digest可能在您的测试中具有其他副作用。

    $timeout.flush 是您正在寻找的。

    https://docs.angularjs.org/api/ngMock/service/$timeout


    要让您的特定测试正常工作:

    it('should error on no foo', function(){
      MyServices.test().should.eventually.be.rejectedWith(TypeError, 'foo is required')
      $rootScope.$apply();
    });
    
    it('should pass on foo', function(){
      MyServices.test('foo').should.eventually.become({});
      $rootScope.$apply();      
    }
    

    tl;dr

    it('async test', () => {
      setup();
      expect();
      execute();
    })
    
    it('sync test', () => {
      setup();
      execute();
      expect();
    })
    

    鉴于已发布的 cmets:

    是否应该提到,对你正在回答的问题投反对票是不道德的?

    很公平。我认为答案具有误导性,因为不需要额外的设置来让 chai-as-promised 与 Angular 一起工作,而不必处理 done 回调。 Fwiw,我将继续尝试撤销所说的反对票并对此保持道德。

    OP 在他的代码中没有超时的迹象,也没有声明任务是异步的。 $rootScope.$digest() 在范围摘要之外调用时对规范没有副作用。在生产环境中不推荐使用它的原因是它没有$apply 所具有的保护措施。

    $rootScope.$digest 实际上与$rootScope.$apply 相同(在这方面还有$scope.$apply)。 source

    $timeout.flush 也将刷新非基于$timeout 的函数。它不是基于$timeout 的函数所独有的。

    Plunker 来展示它是如何正常工作的™plunker

    【讨论】:

    • OP 在他的代码中没有超时的迹象,也没有声明任务是异步的。 $rootScope.$digest() 在范围摘要之外调用时,规范中 没有 副作用。在生产中不推荐使用它的原因是它没有$apply 拥有的保护措施。
    • @estus 我已尽力回答您的疑虑。此外,如果您不确定这种方法是否适用于“同步”任务,请查看plunker
    • 当然,$timeout.flush() 有效,但在这里看起来有点矫枉过正,而且矫枉过正不是应该在规范中机械地完成的事情。如果存在未刷新的超时,测试人员可能会意识到它们并有意识地刷新它们。否则在eventually 之后仅仅一个$rootScope.$digest() 就足够了。
    • 这基本上就是 transferPromiseness 钩子所做的,我已经更新了答案以表明规范与它和 shouldn't return a promise 同步。如果他/她觉得合适,可以将$timeout.flush() 而不是$digest() 放入transferPromiseness,尽管出于上述原因我不建议这样做。
    猜你喜欢
    • 2016-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-02
    • 1970-01-01
    • 2018-06-01
    相关资源
    最近更新 更多