willDestroy in acceptance tests doesn't operate on the real object?


#1

I’m trying to write an acceptance test for my app, and it seems to me as if teardown / willDestroy isn’t working the way I expect it to. Repro steps: (ember-cli v0.1.12):

ember init

ember g route foo

ember g controller foo

Edit templates/foo.hbs to contain:

{{outlet}}
{{#each thing in things}}
   <div class="thing">
   {{thing}}
   </div>
{{/each}}

Edit controllers/foo.js to contain:

import Ember from 'ember';
export default Ember.Controller.extend({
  things: [],
  init: function() {
    this.get("things").pushObjects([1,2,3]);
  }
});

“ember serve”, go to localhost:4200, and you see

Welcome to Ember.js
1
2  
3

just as you’d expect. So far, so good. Now, let’s try testing this.

“ember g acceptance-test foo-test”, and add the following test to tests/acceptance/foo-test:

test('visiting /foo', function() {
  visit('/foo');
  andThen(function() {
    equal(currentPath(), 'foo');
    equal(find(".thing").length, 3);
  });
});

Go to localhost:4200/tests, the test passes just fine – we have three things in our controller, so we get three divs with that class.

Now, go back to the acceptance test, and copy/paste that test so we now have two tests:

test('visiting /foo again', function() {
  visit('/foo');
  andThen(function() {
    equal(currentPath(), 'foo');
    equal(find(".thing").length, 3);
  });
});

This time, the tests fail – now it says

Acceptance: Foo: visiting /foo again (1, 1, 2) Rerun 223 ms
1. failed
  Expected: 	
  3
  Result: 	
  6 

This is happening because the second time through, we call init() again, and it adds three more things to the list of things.

Question 1: isn’t that what the setup/teardown stuff around acceptance tests is meant to do? The top of tests/acceptance/foo-test.js contains:

module('Acceptance: Foo', {
  setup: function() {
    application = startApp();
  },
  teardown: function() {
    Ember.run(application, 'destroy');
  }
});

so I had assumed that calling Ember.run to destroy the application would, well, destroy the application, so that my second test would be starting fresh.

But maybe it doesn’t actually completely destroy it, maybe it relies on my code to help clean things up – so I add code to my controller for that. In controllers/foo.js, add:

  willDestroy: function () {
    console.log("in willDestroy in controller");
    this.set("things", []);
  }

and re-run the tests. Now, surely, things should be okay, right? I’m explicitly clearing out the list of things, so the second time the test runs, it should start with a clean list. But it doesn’t – the tests fail in exactly the same way, with 6 items in the list. Even stranger, if you look at the console logs, it does log “in willDestroy in controller”, and if you add more logging to confirm the length of the ‘things’ array in willDestroy, it goes from three back to zero – but somehow, the second test gets a controller which starts off with three items in “things”.

Question 2: What’s going on here?

I can fix this by making my init() function check to only add to the list if this.get(“things.length”) === 0, but I don’t think I should have to do that, surely?

It feels as if the object that willDestroy is operating on isn’t “the real object”, or something like that, and it’s not specific to arrays, even – if I set a number to 1 in init(), then back to 0 in willDestroy(), then the willDestroy() object thinks the number is 0, but the next time init() is called, the value is back to 1 again.