1
votes

I've hit a very weird problem: I'm trying to make unit tests to achieve 100% testing coverage on my application. And of course I wrote some tests for my controllers but it seems like there is no way to test anything async in Ember (2.4.0) using ember-cli.

  1. I have a function in controller that does this:

    readObject() {
       this.store.findRecord('myModel',1).then(function(obj) {
          this.set('property1',obj.get('property2');
       }.bind(this));
    }
    
  2. I'm writing a test that should cover this function.

    test('action readObject', function (assert) {
       const cont = this.subject();
       cont.readObject();
       assert.equal(cont.get('property1'), 'someValue);
    });
    
  3. Obivously, this assert wouldn't work because readObject() is async call but this isn't the root of the problem. The problem is that then a callback in this.store.findRecord is being executed - my controller is already destroyed! So I get "calling set on destroyed object" error there.

In other words - even if I wrap my function in a promise and reformat both functions like this:

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

and

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

It wouldn't work, because after executing readObject() my controllers become immediately destroyed, not waiting for any callbacks. So, it could be any async call instead of store.findRecord - it could be Ember.run.later, for example.

Does anybody had the same issue? I've read a lot of articles I can't believe that Ember with such a big community doesn't provide a way to make async unit tests.

If anyone has any clues - please give me a hint, cause I'm kinda lost here. At the moment I have two thoughts:

  1. I'm making controllers wrong, Ember doesn't suppose any async operations inside of it. But even if I move async calls to services - I hit the same problem with writing unit tests for them.

  2. I have to decompose my functions to

    readObject() {
      this.store.findRecord('myModel',1).then(this.actualReadObject.bind(this));
    }
    
    actualReadObject(obj) {
       this.set('property1',obj.get('property2');
    }
    

to have at least callbacks body covered with tests, but this means I never get 100% testing coverage in my app.

Thank you in advance for any clues. :)

2

2 Answers

1
votes

I had a similar problem and looking at the QUnit API - async I solved it. Try out following:

// ... 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; 

In a case of unit tests I do also mock other dependencies, for example: 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" }));
        })
      }
   }
});

I am not sure about the second case (the one without assert.async). I think it would work too, because the test suite returns a promise. This gets recorgnized by QUnit that waits for the promise.

0
votes

I copy my own solution here, cause code formatting in comments isn't too good.

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

So, I just turned store.findRecord into a sync function and it runs perfect. And again - many thanks to Pavol for a clue. :)