I've set up an error substate route/controller/template according to http://guides.emberjs.com/v2.2.0/routing/loading-and-error-substates/. Manually browsing my app, I can trigger error conditions and get directed to the substate. Confirmed with Ember Inspector.
I'd like to automatically test the substate. However, Ember CLI's test runner fails any test when a route's model hook rejects. In other words, the test fails before I can navigate to the error substate.
How can I automatically test my error substate?
Ember: 2.2.0
Ember CLI: 1.13.13
Unfortunately it doesn't seem to be easy to do this in a clean manner.
In its internal tests, Ember uses bootApplication to the route which errors (see github) and is able to directly catch the error. Unfortunately if you try and do any form of try/catch or then/catch around a call to visit in your tests you will find it fails.
When you visit a link which results in an error substate from your acceptance test then Ember's defaultActionHandlers.error gets fired. By design it is not meant to be overridable. It calls logError which calls Ember.default.Logger.error.
So to test this substate we need to temporarily overwrite that method. We can also peek inside the ember container to access the currentRouteName like so (using sinon for the spying):
test('when there is an API error an error message is shown', function(assert) {
const emberLoggerError = Ember.Logger.error;
Ember.Logger.error = sinon.spy();
visit('/users/');
andThen(() => {
// This could be nicer and less private with `getOwner`
let { currentRouteName } = this.application.__container__.lookup('router:main');
assert.equal(currentRouteName, 'users.index_error', 'The current route name is correct');
assert.equal(Ember.Logger.error.callCount, 1, 'The error logger was called');
// Restore the Ember.Logger
Ember.Logger.error = emberLoggerError;
});
});
Things can get even more complicated though. If your visit happens inside a Promise (it did in our case because we were using ember-page-object for our tests) then you have more to deal with...
In a separate loop onerrorDefault of RSVP is triggered which calls Test.adapter.exception AND Ember.default.Logger.error (again!) - passing the stack. So in this case you need to stub and spy on Test.adapter.exception and expect Ember.default.Logger.error to have been called twice!
Related
I have been trying to configure offline unit tests for polymer web components that use the latest release of Firebase distributed database. Some of my tests are passing, but others—that look nigh identical to passing ones—are not running properly.
I have set up a project on github that demonstrates my configuration, and I'll provide some more commentary below.
Sample:
https://github.com/doctor-g/wct-firebase-demo
In that project, there are two suites of tests that work fine. The simplest is offline-test, which doesn't use web components at all. It simply shows that it's possible to use the firebase database's offline mode to run some unit tests. The heart of this trick is the in the suiteSetup method shown below—a trick I picked up from nfarina's work on firebase-server.
suiteSetup(function() {
app = firebase.initializeApp({
apiKey: 'fake',
authDomain: 'fake',
databaseURL: 'https://fakeserver.firebaseio.com',
storageBucket: 'fake'
});
db = app.database();
db.goOffline();
});
All the tests in offline-test pass.
The next suite is wct-firebase-demo-app_test.html, which test the eponymous web component. This suite contains a series of unit tests that are set up like offline-test and that pass. Following the idea of dependency injection, the wct-firebase-demo-app component has a database attribute into which is passed the firebase database reference, and this is used to make all the firebase calls. Here's an example from the suite:
test('offline set string from web component attribute', function(done) {
element.database = db;
element.database.ref('foo').set('bar');
element.database.ref('foo').once('value', function(snapshot) {
assert.equal(snapshot.val(), 'bar');
done();
});
});
I have some very simple methods in the component as well, in my attempt to triangulate toward the broken pieces I'll talk about in a moment. Suffice it to say that this test passes:
test('offline push string from web component function', function(done) {
element.database = db;
let resultRef = element.pushIt('foo', 'bar');
element.database.ref('foo').once('value', function(snapshot) {
assert.equal(snapshot.val()[resultRef.key], 'bar');
done();
});
});
and is backed by this implementation in wct-firebase-demo-app:
pushIt: function(at, value) {
return this.database.ref(at).push(value);
},
Once again, these all pass. Now we get to the real quandary. There's a suite of tests for another element, x-element, which has a method pushData:
pushData: function(at, data) {
this.database.ref(at).push(data);
}
The test for this method is the only test in its suite:
test('pushData has an effect', function(done) {
element.database = db;
element.pushData('foo', 'xyz');
db.ref('foo').once('value', function(snapshot) {
expect(snapshot.val()).not.to.be.empty;
done();
});
});
This test does not pass. While this test is running, the console comes up with an error message:
Your API key is invalid, please check you have copied it correctly.
By setting some breakpoints and walking through the execution, it seems to me that this error comes up after the call to once but before the callback is triggered. Note, again, this doesn't happen with the same test structure described above that's in wct-firebase-demo-app.
That's where I'm stuck. Why do offline-test and wct-firebase-demo-app_test suites work fine, but I get this API key error in x-element_test? The only other clue I have is that if I copy in a valid API key into my initializeApp configuration, then I get a test timeout instead.
UPDATE:
Here is a (patched-together) image of my console log when running the tests.:
To illustrate the issue brought up by tony19 below, here's the console log with just pushData has an effect in x-element_test commented out:
The offline-test results are apparently false positives. If you check the Chrome console, offline-test actually throws the same error:
The error doesn't affect the test results most likely because the API key validation occurs asynchronously after the test has already completed. If you could somehow hook into that validation, you'd be able to to catch the error in your tests.
Commenting out all tests except for offline firebase is ok shows the error still occurring, which points to suiteSetup(). Narrowing the problem down further by commenting 2 of the 3 function calls in the setup, we'll see the error is caused by the call to firebase.initializeApp() (and not necessarily related to once() as you had suspected).
One workaround to consider is wrapping the Firebase library in a class/interface, and mocking that for unit tests.
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.
working in ember-cli testing. After all tests passed it returns extra two test with errors.
Uncaught Error: Assertion Failed: calling set on destroyed object
Source : '../dist/assets/vendor.js:13269'
this is one unit test configuration
import Ember from "ember";
import { test,moduleFor } from 'ember-qunit';
import startApp from '../helpers/start-app';
var App;
module('An Integration test',{
setup:function(){
App=startApp();
},
teardown: function() {
Ember.run(App, 'destroy');
}
});
This is either because in the result of a promise or any other deferred code you do not check the destroy status of an object, or because you didn't teardown something that has been setup and interact with DOM events or anything external to the core of Ember.
I used to have this especially on some jQuery plugins which I mapped to Ember, and during the tests the plugins were destroying too slowly and I was then either not using a run loop, or not checking the destroyed status of the Ember object I was manipulating.
You can do so with:
if ( !(obj.get('isDestroyed') || obj.get('isDestroying')) ) {
// do your destroying code setting stuff
}
Also think about destroying any jQuery plugins that might have been initialised in the code of your views (anything setup in didInsertElement should be teardown in willDestroyElement for example).
Ok i struggled with similar thing. So basically when you have "this.set()" inside a promise, it might happen that the promise takes too long to resolve, and the user already clicked away from that page, in this case you are trying to set something, that is already destroyed. I found the simplest solution to be just a simple check in the beginning of the promise.
if (this.isDestroyed) {
return;
}
this.set('...');
...
Edit: alternatively you can use Ember.trySet.
The issue is related to a promise not completely resolving and another test getting run immediately after.
You should give Ember Concurrency a try.
import { task, timeout } from 'ember-concurrency';
myFunction: task(function * () {
// do somethinng
yield timeout(1000); // wait for x milliseconds
// do something else
}).drop(),
I had a similar issue in an integration test. To resolve, in the integration test, I waited before performing the next action.
import wait from 'ember-test-helpers/wait';
wait().then(() => {
// perform action (which previously used to cause an exception)
});
Using ember.js v 1.5.1.
I use karma and qunit to test my ember application. In several of my tests I have situations where 1.a user clicks->2.an async call is made to our server->and then 3. a transition via "this.transitionToRoute('someroute')" in the controller is called. When it hits the transitionToRoute method while testing, karma hangs. Tried wrapping it with an ember.run call but didn't seem to help.
When I comment out the transition call it runs, and fails accordingly.
Example Test Code where it hangs and doesn't reach equal calls
test('successful registration request', function() {
setupMockRegistrationRequests();
visit("/register")
.fillIn('#email', 'test2')
.fillIn('#password','password')
.click('#submit')
.andThen(function() {
equal(find(".register-page .form-alert").length, 0, "Should be no error");
equal(find(".login-page").length, 1, "Should be on login screen");
});
});
Controller Code
Test case runs
//this.transitionToRoute('login');
Test case hangs
this.transitionToRoute('login');
Any body know why it is hanging?/What I can do to allow it continue?
The problem was that it was transitioning, but there were more async requests being made by the next route that were not being handled by my mockjax requests. This caused the testing environment to hang without any errors being thrown.
I am working on converting a Backbone application into an Ember application using Ember Data. It works fine in the browser but the Jasmine test cases will not pass. When I try to create a record in the Jasmine test case I get this error:
TypeError: 'undefined' is not a function (evaluating 'type._create({ store: this })') in http://localhost:8888/spec/javascripts/generated/assets/application.js (line 26874)
This is the actual code that the error message points to:
createRecord: function(type, properties, transaction) {
properties = properties || {};
// Create a new instance of the model `type` and put it
// into the specified `transaction`. If no transaction is
// specified, the default transaction will be used.
//
// NOTE: A `transaction` is specified when the
// `transaction.createRecord` API is used.
var record = type._create({
store: this // line 26874
});
The actual code that the test case is executing looks like this:
nutrient = App.Nutrient.createRecord({"name_min":"nut 1","female_31_50_min":7.5,"male_31_50_min":8.0,"created_at":"2011-10-10T01:31:53Z","female_51_70_min":8.5,"updated_at":"2011-10-12T12:28:35Z","male_70_plus_min":10.0,"female_19_30_min":6.5,"child_4_8_min":4.0,"male_19_30_min":7.0,"lactating_14_18_min":5.75,"infant_0_05_min":1.0,"female_70_plus_min":9.5,"pregnant_14_18_min":5.8,"infant_6_12_min":2.0,"id":1,"male_9_13_min":5.0,"child_1_3_min":3.0,"female_9_13_min":4.5,"female_14_18_min":5.5,"male_14_18_min":6.0,"lactating_31_50_min":7.75,"pregnant_31_50_min":7.8,"pregnant_19_30_min":6.8,"male_51_70_min":9.0,"lactating_19_30_min":6.75,"female_31_50_max":8.5,"male_31_50_max":9.0,"female_51_70_max":9.5,"male_70_plus_max":11.0,"female_19_30_max":7.5,"child_4_8_max":5.0,"male_19_30_max":8.0,"lactating_14_18_max":6.75,"infant_0_05_max":2.0,"female_70_plus_max":10.5,"pregnant_14_18_max":6.8,"infant_6_12_max":3.0,"male_9_13_max":6.0,"child_1_3_max":4.0,"female_9_13_max":5.5,"female_14_18_max":6.5,"male_14_18_max":7.0,"lactating_31_50_max":8.75,"pregnant_31_50_max":9.8,"pregnant_19_30_max":7.8,"male_51_70_max":10.0,"lactating_19_30_max":7.75})
person = new App.Person.createRecord({age: 0.25})
expect(nutrient.requiredNutrientForPerson(person)).toEqual({min_amount: 1.0, max_amount: 2.0})
Any ideas would be appreciate.
In general, if you're having problems with a test that you don't experience in the browser, it's because the tests are running outside of the Ember run loop.
Try calling Ember.run.sync() before expect() to force synchronization. Alternatively, place any code that involves binding in an anonymous fn inside: Ember.run(function() { }).
Check out the ember and ember-data source for other testing examples, since coverage is pretty solid.
With that said, I'm not an ember-data expert, so I'm not sure if this is the problem you're experiencing.
Sorry, My Bad. The problem is with this line:
person = new App.Person.createRecord({age: 0.25})
I needed to remove the new keyword and it worked correctly