在Ember控制器中单元测试异步功能

时间:2016-04-12 13:24:08

标签: javascript unit-testing asynchronous ember.js qunit

我遇到了一个非常奇怪的问题:我正在尝试进行单元测试以在我的应用程序上实现100%的测试覆盖率。 当然我为我的控制器编写了一些测试,但似乎没有办法使用ember-cli在Ember(2.4.0)中测试任何异步。

  1. 我在控制器中有一个功能:

    readObject() {
       this.store.findRecord('myModel',1).then(function(obj) {
          this.set('property1',obj.get('property2');
       }.bind(this));
    }
    
  2. 我正在编写一个应该涵盖此功能的测试。

    test('action readObject', function (assert) {
       const cont = this.subject();
       cont.readObject();
       assert.equal(cont.get('property1'), 'someValue);
    });
    
  3. 很明显,这个断言不起作用,因为readObject()是异步调用,但这不是问题的根源。问题是然后正在执行this.store.findRecord中的回调 - 我的控制器已被销毁!所以我得到了“在被破坏的对象上调用set”错误。

  4. 换句话说 - 即使我将我的函数包装在一个promise中并重新格式化这两个函数:

    readObject() {
       return new Promise(function(resolve) {
           this.store.findRecord('myModel',1).then(function(obj) {
              this.set('property1',obj.get('property2');
              resolve();
           }.bind(this));
       }.bind(this));
    }
    

    test('action readObject', function (assert) {
      const cont = this.subject();
      cont.readObject().then(function() {
          assert.equal(cont.get('property1'), 'someValue);
      });
    });
    

    它不起作用,因为在执行readObject()后,我的控制器立即被销毁,而不是等待任何回调。 因此,它可以是任何异步调用而不是store.findRecord - 例如,它可以是Ember.run.later。

    有人有同样的问题吗?我读过很多文章,我不敢相信Ember这么大的社区没有提供进行异步单元测试的方法。

    如果有人有任何线索 - 请给我一个提示,因为我有点迷失在这里。目前我有两个想法:

    1. 我让控制器出错了,Ember并没有假设其中有任何异步操作。但即使我将异步调用移到服务上 - 我为它们编写单元测试也遇到了同样的问题。

    2. 我必须将我的功能分解为

      readObject() {
        this.store.findRecord('myModel',1).then(this.actualReadObject.bind(this));
      }
      
      actualReadObject(obj) {
         this.set('property1',obj.get('property2');
      }
      
    3. 让至少回调体覆盖测试,但这意味着我从未在我的应用程序中获得100%的测试覆盖率。

      提前感谢您提供任何线索。 :)

2 个答案:

答案 0 :(得分:1)

我遇到了类似的问题,看着QUnit API - async我解决了它。试试以下:

// ... in your controller ...
readObject() {
   return this.store.findRecord('myModel',1).then(function(obj) {
      this.set('property1', obj.get('property2');
   }.bind(this));
}

// ... in your tests, either with assert.async: ...
const done = assert.async(); // asynchronous test due to promises usage
Ember.run(function(){
    subject.readObject().then(function(){
      // once you reach this point you are sure your promise has been resolved
      done();
   });
});


// or without assert.async
let promise;
Ember.run(function(){
    promise = subject.readObject();
});
return promise; 

在单元测试的情况下,我也会模拟其他依赖项,例如:store。

this.subject({
   property1: null, 
   store: {
      findRecord(modelName, id){
         assert.equal(modelName, "myModel1");
         assert.equal(id, 1);
         return new Ember.RSVP.Promise(function(resolve){
            resolve(Ember.Object.create({ property2: "a simple or complex mock" }));
        })
      }
   }
});

我不确定第二种情况(没有assert.async的情况)。我认为它也会起作用,因为测试套件会返回一个承诺。这会被等待承诺的QUnit所记录。

答案 1 :(得分:0)

我在这里复制我自己的解决方案,导致评论中的代码格式化不太好。

test('async', function (assert) {

   const cont = this.subject();
   const myModel = cont.get('store').createRecord('myModel'); // 

   // Make store.findRecord sync
   cont.set('store',{
       findRecord(){
         return { then : function(callback) { callback(myModel); } }
       }
   });

   // Sync tests
   assert.equal(cont.get('property2'), undefined);
   cont.readObject(); // This line calls store.findRecord underneath
   assert.equal(cont.get('property2'), true);
});

所以,我只是将store.findRecord变成了一个同步函数,它运行得很完美。再一次 - 非常感谢Pavol提供线索。 :)