Ember/emberfire run loop acceptance test - ember.js

So my acceptance test keeps on destroying itself before my promise finishes. I'm aware that I need to wrap my promise in Ember run loop but I just can't get it to work. Here's how my component looks:
export default Ember.Component.extend({
store: Ember.inject.service(),
didReceiveAttrs() {
this.handleSearchQueryChange();
},
/**
* Search based on the searchQuery
*/
handleSearchQueryChange() {
this.get('store').query('animals', {
orderBy: 'name',
startAt: this.attrs.searchQuery
}).then(searchResults => {
this.set('searchResults', searchResults);
});
}
});
I've already tried wrapping this.handleSearchQueryChange(), this.get('store').query... and this.set('searchResults', searchResults) in a run loop but still, the acceptance test just doesn't wait for store.query to finish.
One thing to note that this store query performs a request on a live Firebase back-end.
I'm currently using Pretender to mock the data and solve this issue. But I'd like to solve it through Ember.run as well. Anyone care to provide a solution?

It sounds like your problem may have the same cause as the errors I've been seeing
tl;dr
To work around this issue, I've been using a custom test waiter. You can install it with ember install ember-cli-test-model-waiter (for Ember v2.0+) and it should just make your test work without any further setup (if not, please file a bug).
Longer answer:
The root cause of this problem is that the ember testing system doesn't know how to handle Firebase's asynchronicity. With most adapters, this isn't a problem, because the testing system instruments AJAX calls and ensures they have completed before proceeding, but this doesn't work with Firebase's websockets communication.
The custom test waiter I mentioned above works by waiting for all models to resolve before proceeding with the test, and so should work with any non-AJAX adapter.

Related

Ember concurrency timeout hanging in qunit

In Ember I have a component that starts a never-ending poll to keep some data up to date. Like so:
export default Component.extend({
pollTask: task(function * () {
while(true) {
yield timeout(this.get('pollRate'));
this.fetchSomeData();
}
}).on('init')
})
This causes a preexisting acceptance test to get stuck in this task and run forever, even though it should be run asynchronously. The test looks like this:
test('my test', async function(assert) {
mockFindRecord(make('fetched-model'));
await visit('/mypage'); // gets stuck here
await typeIn('input', 'abc123');
assert.ok(somethingAboutThePage);
});
I thought at first that I had mocked the request wrong and that the test was just timing out, but it was in fact correctly polling data. Removing this task makes the acceptance test finish normally.
Testing this manually seems to work fine, and nothing gets stuck. Why is this happening and what is the right way to address this?
Saw Unit testing ember-concurrency tasks and yields but it doesn't really help since it only deals with unit tests.
You're not doing anything wrong and this is a common gotcha with ember-concurrency. Ember-concurrency's timeout() function relies on Ember.run.later() to create the timeout and fortunately or unfortunately, Ember's test suite is aware of all timers created with Ember.run.later() and will wait for all timers to settle before letting the test continue. Since your code is using an infinite loop your timers will never settle so the test hangs. There's a nice little section about testing asynchronous code in the Ember guides here.
There's a section in the ember-concurrency docs about this exact problem here. I recommend you look through it to see their recommendations on how to tackle this although it seems as if there's no real elegant solution at the time.
The quickest and probably easiest way to get this to not hang would be to throw in a check to see if the app is being tested (gross, I know):
pollTask: task(function * () {
while(true) {
yield timeout(this.get('pollRate'));
this.fetchSomeData();
if (Ember.testing) return; // stop polling to prevent tests from hanging
}
}).on('init')
You can also try to throw in a call to Ember.run.cancelTimers() in your tests/helpers/start-app.js file (another suggestion in that section):
window._breakTimerLoopsId = Ember.run.later(() => {
Ember.run.cancelTimers();
}, 500);
But it doesn't seem to appear in the API docs so I personally wouldn't rely on it.

Some questions about async in Ember

I'm working with emberjs during some time and now I want to refine my understanding of ember's nature.
Question 1.
Am I right that the route's model() hook is the best place to write asynchronous code (to get all needed data)? Should I try to place all my network requests (or at least most of them) inside routes' models?
Question 2.
The component hooks have synchronous nature. Does that mean it's a bad decision to write async code inside hooks?
Say, I have async init() hook where some data is calculated and didRender() hook where I expect to use that data. When ember calls init() it returns a Promise, so it's moved from a stack to a special queue and ember doesn't wait until event loop returns that code back to a stack. So ember runs next hooks, and when didRender() is being executed the init() hook may not be fulfilled and the expected data may not exist. Is that right?
Question 3.
Services hooks should also be synchronous. Because when a service is injected inside a component and is used ember also doesn't wait until the async hook is fulfilled.
Say, I have a shopping cart service with products property. The products ids are stored in localstorage and I want to get those products from a server to set them into products property.
import Service from '#ember/service';
import { A } from '#ember/array';
import { inject as service } from '#ember/service';
export default Service.extend({
store: service(),
init(...args) {
this._super(args);
this.set('products', A());
},
async loadProducts() {
const cartProducts = A();
const savedCartProducts = localStorage.getItem('cartProducts');
if (savedCartProducts) {
const parsedSavedCartProducts = JSON.parse(savedCartProducts);
const ids = Object.keys(parsedSavedCartProducts);
if (ids.length > 0) {
const products = await this.store.query('product', {id: Object.keys(parsedSavedCartProducts)});
products.forEach(p => {
p.set('cartQuantity', Number(parsedSavedCartProducts[p.id].qty));
cartProducts.pushObject(p);
});
}
}
this.products.pushObjects(cartProducts);
},
...
});
If I call loadProducts() from service's init() I can't use this.cart.products in controllers/application.js, for example. Because service is ready, but async init() is still executed. So, should I call it in routes/application.js model() hook?
Question 4.
If there is some general component that doesn't refer to any route, but this component needs some data from a server where should I make async requests? Or are computed properties and observers are the only solutions?
Thanks a lot.
Good questions here, you’re on the right track!
Question 1: yes, the general rule of thumb is that until you are familiar with things, doing async work in the route hooks (beforeModel, model and afterModel) make it easier to think about what is going on. Eventually you may want to start bending the defaults to suit your custom UI needs, but it’s simplest to start with this rule
Questions 2-4: you’re asking several questions here about async code, so will answer here more broadly.
First off, services can do async or not but unless you call those methods from a hook (like a route hook) you are responsible for handling the asynchronous results. For instance, the Ember Data store is a service that does return data asynchronously
But if you hit cases where you do need to do async in a component, the recommended addon to help with that is Ember Concurrency: http://ember-concurrency.com/docs/introduction/
Ember Concurrency helps solve many of the async bugs and edge cases that you haven’t yet hit, but will, if you start doing async code in components. So I’d highly recommend learning more about it.
Good luck!

Ember 3.0 acceptance redirect test hangs forever

I have a simple acceptance test written in the modern RFC 268 format for Ember 3.0.
The test is for a page where, if the user is unauthenticated, the URL immediately redirects to /login.
...
module('Acceptance | index', function (hooks) {
setupApplicationTest(hooks);
test('visiting / logged out', async function(assert) {
assert.expect(1);
// Test hangs here forever.
await visit('/');
assert.equal(currentURL(), '/login');
});
});
This test worked great using the older format with moduleForAcceptance.
Unfortunately, this test hangs forever in Ember 3.0. Am I missing something here? Is there a better way to test a redirect?
There are no errors in the console, and the addition of some console.log statements show that the await is where the test hangs.
I found the reason why this was failing. I have an Ember mixin that I use to enhance all of my routes. The mixin checks for whether or not a user is authenticated, and redirects to /login as needed.
export default Mixin.create({
session: service(),
beforeModel() {
this._super(...arguments);
return new EmberPromise((resolve) => {
if (authenticated) {
resolve();
return;
}
this.transitionTo('login');
});
}
});
You'll notice that I am not resolving if authenticated is falsey. That worked fine with my app and the test syntax in 2.18.
The docs say the following regarding that hook I am overriding in my mixin.
returns Any | Promise
if the value returned from this hook is a promise, the transition will pause until the transition resolves. Otherwise, non-promise return values are not utilized in any way.
To me, the bit about "non-promise return values" implies that I should be able to do what I'm doing. Especially considering this worked in 2.18, but I wonder if this was one of those "wow, how did that ever work in the first place" scenarios. Clearly this syntax isn't working in 3.0. since the transition pauses forever when testing.
The answer for me was to ensure I always resolve/reject something. In this case, I had to add an explicit reject() so that the promise chain doesn't hang.
export default Mixin.create({
session: service(),
beforeModel() {
this._super(...arguments);
return new EmberPromise((resolve) => {
if (authenticated) {
resolve();
return;
}
this.transitionTo('login');
reject();
});
}
});
My test was fine. It was the mixin that needed updating in order to work properly with Ember 3.0 and the latest testing syntax.
The issue was not what your beforeModel hook resolves if user is authenticated but that your Promise does not resolve at all. You don't have to return a Promise in beforeModel hook but if you return one it will block the transition until the Promise is resolved. Since it's not clear how ember should react if another transition is called while current transition is blocked (not resolved/rejected promise), resolving or rejecting is correct behavior. Please have in mind that in a Promise return does not have any other meaning than ending your execution. It does not resolve or reject your Promise.
Another reason visit() could hang is that it waits for timers like Ember.run.later() to resolve, causing a non-obvious block somewhere in the application.
AlphaGit on github summarized the issue with an example, saying:
Most of the actions that Ember.testing executes (like visit) will
append to a promise that gets executed action after action in the
right order. In order to pass to the next action, Ember.testing makes
sure that there is nothing pending, so that the step can be considered
complete and move forward.
Along with the things that are tested for, pending AJAX requests are
verified, and also scheduled timers. These timers may arise from, you
guessed it, Ember.run.later calls. If for any reason you would have in
your code periodic Ember.run.later methods (so that one is always
waiting to be excuted), it's likely that you'll face this issue.
I've faced it myself in a similar scenario: My server returns a OAuth
access token with 100 hours until expired, so ember-simpleAuth
registers a call close to the expiration time with Ember.run.later to
refresh the token. This will, however, prevent the test from moving
along. My specific situations has been fixed in further versions but
any similar behavior will reproduce the issue (which is likely a
conclusion of the current design of Ember.testing).
Here are a couple other examples of users running into similar issues:
https://stackoverflow.com/a/58526993/3257984
https://stackoverflow.com/a/27887807/3257984

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 :(

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){...}.