Why does this controller seem to "hold onto state" even between tests? - ember.js

I'm writing a simple qunit test that has a controller setup w/ a few items in an array
App.UploadController = Ember.Controller.extend({
to_upload: Ember.A([])
});
I add stuff to this "to_upload" array and verify it between tests. One issue I've found that that if I don't "manually clear it" like this in my test "tear down" it holds onto this state (even with an App.reset() being called in each teardown (calling destroy).
App.__container__.lookup("controller:upload").clear_files()
(the clear files is a simple monkey patch I added in my test helper -not production code)
App.UploadController.reopen
clear_files: function() {
this.get('to_upload').clear();
}
Here is my tear down for the qunit tests
module "/upload",
teardown: function() {
App.reset()
App.__container__.lookup("controller:upload").clear_files()
}

It's probably related to the fact that assigning an array to a property in the class definition makes it a class property and not an instance property. I'm not sure if this is documented somewhere, I've read it before a few times though. I'll update with documentation if I can find it.
Until then you can probably prove my theory by modifying the code to look like this.
App.UploadController = Ember.Controller.extend({
init: function(){
this.set('to_upload', Em.A([]));
this._super();
},
to_upload: null
});
You can read about it in step 6 here http://codebrief.com/2012/03/eight-ember-dot-js-gotchas-with-workarounds/
I'm pretty sure this should be put somewhere in the docs, I'm not sure where the most obvious would be though.

Related

Ember Test for parent route with method calls

I was brought in on the "back-end" of a project and asked to help write tests for an app. I am very new to Ember and need just a little help getting started. We are trying to provide unit test for the routes, so we can have a bit more molecular scope over the app, instead of acceptance test. I have looked at some tutorials and went through every possible scenario that I could think of. I just need a bit of jumpstart.
Here is the part of the route.js for this route.
down stream of this parent route, we have another nested route that shows a list of contacts and when a user clicks on a show button it calls "model" and returns that variable "rec" for the template and the url
export default Route.extend(ScrollTo, {
flashMessages: service(),
model: function(params) {
let rec= this.store.peekRecord('contact', params.contact_id);
return rec;
},
actions: {
saveContact: function() {
let model = this.currentRouteModel();
model
.save()
.then(() => {
//this.refresh();
//this.setModelHash();
this.flashMessages
.success(`Saved Contact: ${model.get('title')}`);
//this.transitionTo('contacts');
});
}
Here is pre-generated code for the test. I really haven't made any modifications, because I really didn't know where to start.
This app doesn't have a back-end, it's design is to take in information and provide a iso file based on whatever standard the user wants.
I am sure I need to provide some mock data for the test and send that to the method, but again I am not sure what Qunit parts I use.
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module('Unit | Route | contact/show', function(hooks) {
setupTest(hooks)
test('it exists', function(assert) {
var route = this.owner.lookup('route:contact/show');
assert.ok(route, 'contact.show route works');
});
});
I struggled with this as well when I first moved to frontend testing. With a few years experience I'm confident in saying you shouldn't unit test the route objects in this way. Instead the testing of an ember app should focus on two types of tests.
What ember calls integration tests (but are actually closer to UI unit tests)
Acceptance tests
Integration tests on components allow you to get into the smaller details and edge cases and they can be super valuable.
What I think you want in this case is actually an acceptance test.
If your testing experience is at all like mine it will feel like you're testing too many things at first, but these types of tests where the browser actually loads the entire application have the most value in hunting down bugs and are the most maintainable over time.

EmberJS 2.13 unit testing: wrapping synchronous code in a run loop?

This is repeated a few times in the current Ember documentation, so I feel like I must be missing something. Let's take the simplest example I found.
Why is the call to levelUp considered asynchronous to warrant wrapping it in the run loop?
incrementProperty is synchronous, and as far as I can tell, so is set (but I could be mistaken here)
player.js
import DS from 'ember-data';
export default DS.Model.extend({
level: DS.attr('number', { defaultValue: 0 }),
levelName: DS.attr('string', { defaultValue: 'Noob' }),
levelUp() {
let newLevel = this.incrementProperty('level');
if (newLevel === 5) {
this.set('levelName', 'Professional');
}
}
});
player-test.js
import { moduleForModel, test } from 'ember-qunit';
import Ember from 'ember';
moduleForModel('player', 'Unit | Model | player', {
// Specify the other units that are required for this test.
needs: []
});
test('should increment level when told to', function(assert) {
// this.subject aliases the createRecord method on the model
const player = this.subject({ level: 4 });
// wrap asynchronous call in run loop
Ember.run(() => player.levelUp());
assert.equal(player.get('level'), 5, 'level gets incremented');
assert.equal(player.get('levelName'), 'Professional', 'new level is called professional');
});
First of all, you are absolutely right. It is not well-described anywhere in the guides.
In testing mode, autorun is disabled. You can read further from the guides about this.
But changing the value in model triggers a run-loop. You can see that at this twiddle. The result is:
Assertion Failed: You have turned on testing mode, which disabled the
run-loop's autorun. You will need to wrap any code with asynchronous
side-effects in a run
(By the way, both set and incrementProperty trigger this run-loop as your guess.)
Then here is the run loop source:
DS.attr returns a computed property with set.
The set function triggers an event.
At the end, a run loop is triggered.
#ykaragol is absolutely right about his explanation within his correct answer and I have nothing to add why you need to wrap your code within a run loop; because the source code is there and emberRun.schedule is being called, which requires a run-loop.
What I would like to explain is a bit more about the assertion error you get: "You have turned on testing mode, which disabled the run-loop's autorun. You will need to wrap any code with asynchronous side-effects in a run". That does not directly mean an asynchronous operation (in the sense that an ajax call is made, or a timer is triggered) is in place. We are mostly unaware but; Ember.js does use Ember.run loops and various run queues such as sync, actions, render, afterRender, etc. in order to schedule the effects of our codes so as to optimize the rendering of our application. Even if the code this.set('levelName', 'Professional'); does seem like pretty synchronous; Ember wraps it within a run-loop so that the computed property calculation, or other updates are buffered together in order to prevent multiple rendering (hence decreased performance) of the templates.
I only wished there was better explanation about both run loop, run queues, or how and why to use run loops within tests, but there is not :(

Ember Integration Test Doesn't See the DOM

import Ember from 'ember';
import startApp from '../../helpers/start-app';
var App;
module('Integration | Authentication | Abilities', {
integration: true,
setup() {
App = startApp();
},
teardown() {
Ember.run(App, 'destroy');
}
});
test('Only SuperUsers, Researchers and AccountHolders can see messages', function(assert) {
visit('/');
assert.equal(find('div').text(), 'fsfsfd');
});
This is an integration test I'm trying to get working so I can test basic user interaction in our ember-cli app. The problem is that this simple test does only returns empty strings whenever I search the DOM. It is not hitting an unauthorized page or anything it's just returning nothing from any testHelpers. currentURL, currentPath return undefined.
Am I missing something absolutely fundamental in my understanding of how integration tests work?
I'm trying to test how ember-can gives and denies permissions to users based on their title. However, I may as well just be testing whether or not the logo shows up in the right corner because I can't see anything on the page at the moment.
I think what you're missing is that tests are asynchronous. Transitions involve promises (typically loading models) so you need to wait for the visit to complete. You can use the andThen helper:
test('Only SuperUsers, Researchers and AccountHolders can see messages', function(assert) {
visit('/');
andThen(function() {
assert.equal(find('div').text(), 'fsfsfd');
});
});
Here's more info in the guides
I'm posting because it turns out the problem was the way our website had been set up with a rootURL. I had to put this line inside our startApp.js file: setResolver(Ember.DefaultResolver.create({ namespace: "/cli/ea/" }));
It looks like the resolver was taking me to localhost:4201/ which is not actually going to be used because we are proxying from rails (which is localhost:3000). Therefore, nothing was coming back from any DOM interaction because there was no route and no template set. currentURL and other helpers returning undefined I guess was the only piece that was unusual in hindsight.

What is the proper way in ember to use scheduleOnce that is test friendly?

Often, we'll need to schedule an update after rendering completes in reaction to a property change. Here is an example:
page_changed: function() {
Ember.run.scheduleOnce('afterRender', this, this.scroll);
}.observes('current_page')
This doesn't work for testing since there is no runloop at the time scheduleOnce is called. We can simply wrap scheduleOnce in an Ember.run
page_changed: function() {
Ember.run(function() {
Ember.run.scheduleOnce('afterRender', that, that.scroll);
});
).observes('current_page')
..but I'm being told that's not the right way to go about it. I thought I'd reach out and get some other ideas.
For reference here is the issue that I opened up in ember.js#10536
Looks like this is the way to do it according to #StefanPenner's comment. Instead of modifying the app code itself just wrap the render call with an Ember.run
test('it renders', function() {
expect(2);
var component = this.subject();
var that = this;
equal(component._state, 'preRender');
// Wrapping render in Ember.run
Ember.run(function() {
that.render();
});
equal(component._state, 'inDOM');
});
the entry point into your application in the test needs the Ember.run. For example
test('foo', function() {
user.set('foo', 1); // may have side-affects
});
test('foo', function() {
Ember.run(function() {
user.set('foo', 1);
});
});
Why is this needed? Ember.run wraps the root of all call-stacks that are triggered by click/user-actions/ajax etc. Why? The run-loop is what allows ember to batch
When writing unit-tests, we are "faking" the user or network actions, it isn't obvious to ember what groups of changes you want to "Batch" together.
We can think of Ember.run as a way to create a batch or transactions of changes. Ultimately we use this, to batch DOM reads/writes to interact with the DOM in an ideal way.
Although maybe frustrating until you get the hang of it, it is a great way to create concise and deterministic tests. For example:
test('a series of grouped things', function() {
Ember.run(function() {
component.set('firstName', 'Stef');
component.set('lastName', 'Penner');
// DOM isn't updated yet
});
// DOM is updated;
// assert the DOM is updated
Ember.run(function() {
component.set('firstName', 'Kris');
component.set('lastName', 'Selden');
// DOM isn't updated yet, (its still Stef Penner)
});
// DOM is once again updated, its now Kris Selden.
});
Why is this cool? Ember gives us fine-grained control, which lets us not only very easily test aspects of our app in isolation, but also test sequences of what may be user or ajax pushed based actions, without needing to incorporate those aspects.
As time goes on, we hope to improve the test helpers and clarity around this.
..but I'm being told that's not the right way to go about it.
The reason is that you need the Ember.run call when the callback is executed, not when it's scheduled. Like this:
Ember.run.scheduleOnce('afterRender', function() {
Ember.run(function() {
that.scroll();
});
});
In your code, Ember.run is being called when the callback is scheduled. This is likely unnecessary as you're probably already in the run loop. My version ensures that when the callback is called, even if the run loop finished long ago, that another run loop is started and that.scroll() is called within the run loop. Does that makes sense?

Errors from debounced function calls in testing ember application

I'm testing an relatively large Ember application (http://wheelmap.org/map) with QUnit and having problems with debounced calls e.g. changing the url to have a permalink of a map view inside the app or doing a manual AJAX request while testing.
I followed the documentation at http://emberjs.com/guides/testing/integration/
Now when I reset the application state by calling App.reset() in the module setup it resets all bindings, etc. to variables and dependant controllers.
module('Map', {
setup: function() {
App.reset();
}
});
This seems to be good to have a clean working environment, but leads to errors where variables are accessiable by Ember.set and Ember.get e.g. this.get('controllers.toolbar'):
Cannot call method 'set' of null
So the first test allways runs great, but further tests break because of debounced function calls from the first test. So what I think I have to do is stop this debounced calls somehow.
Other options would be checking if all needed variables are set in this function calls. But this seems to be cumbersome when adding conditions only for testing.
What do you think?
Thank you in advance!
I found the answer by searching through the RunLoop source files:
Ember.run.cancelTimers()
It's not part of the documentation. Maybe a problem of poor documentation or not beeing part of the public API.
Now I just call it in the module test teardown function:
module('Map', {
setup: function() {
// ...
},
teardown: function() {
Ember.run.cancelTimers()
}
});
We ran into a similar problem and decided to disable debounce during testing.
You can check if in testing mode using if(Ember.testing){...}.