In my Ember app, I currently have a model that has a findResults
function that returns a promise that is wrapping a Google Places library to fetch results for auto-completion. To use this in my UI, I setup a PromiseMixin controller. I instruct the controller to watch the searchText
value, and when that changes I update the controller's promise value to be the promise returned by the findResults
function, but with the new value from searchText
. This works nicely when I'm playing with the app in the browser, however when I run my acceptance tests the test seems to finish before the promise is returned and therefore the tests fail. I'll include the relevant files below.
I'm not sure how to tell Ember to wait for the promise to resolve during testing.
app/services/google-autocomplete-location.js
import Ember from "ember";
var googleAutocompleteLocation = Ember.Object.extend({
placeId: null,
description: null
});
googleAutocompleteLocation.reopenClass({
findResults: function(query) {
var self = this;
var promise = new Ember.RSVP.Promise(function(resolve, reject) {
var autocompleteService = new google.maps.places.AutocompleteService();
return autocompleteService.getPlacePredictions({ input: query },
function(predictions, status) {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
Ember.run(null, reject, status);
}
else {
Ember.run(null, resolve, self._decorateGoogleResults(predictions));
}
});
});
return promise;
},
_decorateGoogleResults: function(predictions) {
var locations = [];
predictions.forEach(function(prediction) {
locations.push(
googleAutocompleteLocation.create({
placeId: prediction.place_id,
description: prediction.description
})
);
});
return locations;
}
});
export default googleAutocompleteLocation;
app/controllers/index.js
import Ember from "ember";
import GoogleLocation from "../services/google-location";
import GoogleAutocompleteLocation from '../services/google-autocomplete-location';
export default Ember.ArrayController.extend(Ember.PromiseProxyMixin, {
searchText: '',
map: null,
mapUrl: null,
actions: {
submit: function() {
return this.transitionToRoute('entries.new');
}
},
highlightedResult: function() {
if (this.get('model').length) {
return this.get('model')[0];
} else {
return null;
}
}.property('model'),
setMap: (function() {
if (this.get('highlightedResult') === null) {
return this.set('map', null);
} else {
if (this.get('map') === null) {
return this.set('map', GoogleLocation.create({
mapContainer: Ember.$('.maps-info'),
placeId: this.get('highlightedResult').placeId
}));
} else {
return this.get('map').set('placeId', this.get('highlightedResult').placeId);
}
}
}).observes('highlightedResult'),
searchTextChanged: (function() {
if (this.get('searchText').length) {
this.set('promise',
GoogleAutocompleteLocation.findResults(this.get('searchText')));
console.log(this.get('promise'));
} else {
this.set('model', []);
}
}).observes('searchText')
});
tests/acceptance/create-new-entry-test.js
test('finding a location', function() {
expect(1);
visit('/');
click('.location-input input');
fillIn('.location-input input', "Los Angeles, CA");
andThen(function() {
var searchResult = find('.search-results ul li:first a').text();
equal(searchResult, 'Los Angeles, CA, United States');
});
});
Visit http://localhost:4200/tests in your browser. When the execution of the test come upon await pauseTest() , the test will be paused, allowing you to inspect the state of your application. You can now type resumeTest() in the console of your browser to continue the test execution.
Ember CLI comes with acceptance test support out of the box. For creating your first test, you just need to run ember generate acceptance-test <name> . In our case, ember generate acceptance-test user-can-login-via-form . Ember CLI will create a new test file under tests/acceptance/ .
The best way to go about this is likely to register your own async test helper. I've prepared a JSBin with a simulation of your code and a solution here: http://jsbin.com/ziceratana/3/edit?html,js,output
The code used to create the helper is this:
Ember.Test.registerAsyncHelper('waitForControllerWithPromise', function(app, controllerName) {
return new Ember.Test.promise(function(resolve) {
// inform the test framework that there is an async operation in progress,
// so it shouldn't consider the test complete
Ember.Test.adapter.asyncStart();
// get a handle to the promise we want to wait on
var controller = app.__container__.lookup('controller:' + controllerName);
var promise = controller.get('promise');
promise.then(function(){
// wait until the afterRender queue to resolve this promise,
// to give any side effects of the promise resolving a chance to
// occur and settle
Ember.run.schedule('afterRender', null, resolve);
// inform the test framework that this async operation is complete
Ember.Test.adapter.asyncEnd();
});
});
});
And it would be used like so:
test('visiting / and searching', function() {
expect(1);
visit('/');
click('.location-input input');
fillIn('.location-input input', "Los Angeles, CA");
waitForControllerWithPromise('index'); // <-- simple & elegant!
andThen(function(){
var searchResult = find('.search-results ul li:first').text();
equal(searchResult, 'Los Angeles, CA, United States');
});
});
In ember-testing, an async-helper will automatically wait on previous promises and subsequent async helpers will wait on it when the test is executed. For excellent background on this, see Cory Forsyth's Demystifying Async Testing
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With