I am writing an integration test for my Ember.js application in QUnit. Before a test, I want to seed some test data by issuing HTTP requests to a dedicated testing API. I use jQuery.post to issue POST requests and I use Ember.RSVP.Promise.cast to turn the jQuery promise into an RSVP promise. However, it never seems to resolve. In the code below, it just hangs. The string "STARTING" is printed but neither "DONE" nor "FAIL" is printed.
I also tried creating a new RSVP Promise as described in the "Advanced usage" section of http://emberjs.com/api/classes/Ember.RSVP.Promise.html, to no avail (it also hanged). If I don't wrap the jQuery promise into an RSVP Promise, it does reach either the "DONE" or "FAIL".
Why doesn't the RSVP Promise resolve?
function create_teacher() {
var url = "<%= testing_teacher_path %>";
return Ember.RSVP.Promise.cast(
Ember.$.post(
url,
{
user: {
first_name: "John",
last_name: "Doe"
school: "EE3",
email: "john#doe.com",
password: "password"
}
}
)
);
}
module("Teacher Dashboard", {
setup: function() {
console.log("STARTING");
Ember.run(HstryEd, HstryEd.advanceReadiness);
},
teardown: function() {
console.log("TEARING DOWN");
HstryEd.reset();
}
});
asyncTest("Login", function() {
expect(1);
var teacher = create_teacher();
teacher.then(function() {
console.log("DONE");
ok(true, "done");
start();
},
function() {
console.log("FAIL");
ok(false, "fail");
start();
});
});
It could have to do with the Ember runloop being disabled in test mode. Have you checked out ic-ajax? https://github.com/instructure/ic-ajax It gives you promise-style jQuery ajax requests in a form that Ember likes, even in testing. I brought it in to solve my Ember runloop issues in testing, and have had great results so far.
Alternatively, you could try wrapping your teacher.then(.. in an Ember.run.
Related
Given the following code snippets, Ember.js keeps hitting the following alert everytime I press the update button on Edit.hbs while in development mode:
alert('failed to update user!')
However, what makes this issue different is that the record is persisted on the server side with a 200 (M'kay) reply, and when I am in production mode with my live website, Ember.js confirms that the user has been updated with the other alert()... Any thoughts on what could be the source of this issue?
I have seen quite a few examples around, but I am suspecting that most of them are no longer valid for the 2.0 ember versions...
Router
this.route('user', { path:'user/:user_id' }, function() {
this.route('edit');
});
Edit Controller
// Removed extra code that controls warning messages to model.user
// and comparison to model.users
update: function(user) {
// Extra Code that checks current_password and others omitted.
user.save().then(function() {
alert('User Updated!');
// transitionToRoute errors with undefined...
this.controller.transitionToRoute('user', user);
}, function() {
alert('failed to update user!');
});
}
Edit Route
export default Ember.Route.extend ({
model: function() {
return Ember.Object.create ({
user: this.modelFor('user'),
users: this.store.findAll('user')
});
}
});
Edit Template (Button Only)
<button class="button_type1" {{action "update" model.user}}>Update User</button>
Live Website
This will take you directly to User #2 edit page. At the moment you will have unrestricted access to the website as this will make it easier for bug hunting... Changing password is not working, but the other attributes should persist as discussed previously.
Source Code
EDIT #1
this.transitionToRoute('user', user); is now working as expected based on Deewendra's answer.
user.save().then(() => {
alert('User Updated!');
this.transitionToRoute('user', user);
}).catch(() => {
alert('User Not Updated!');
});
EDIT #2
By appending reason to the catch statement, I was able to narrow down the second issue that I was having:
user.save().then(() => {
// Not reaching
}).catch((reason) => {
console.log(reason); //Assertion Failed: normalizeResponse must return a valid JSON API document: * Top level of a JSON API document must be an object.
});
The server terminal kept telling me 200 OK, but the actual response was badly formatted on the Rails side. If you encounter a similar problem, then make the following changes on your Rails controller:
// Outputs Bad JSON
render json: user.update(user_params)
// Output proper JSON
user.update(user_params)
render json: user
The issue lies in the controller code. When you do user.save(), it returns a promise and for any function you call on it, the context will be the promise itself so in your then block 'this' points to the promise object and not the router instance so it cannot resolve this.controller and hence although it saves your model, the control goes to the catch/fail block. To solve this you can do :
1) bind context to the 'then' function
update: function(user) {
// Extra Code that checks current_password and others omitted.
user.save().then(function() {
alert('User Updated!');
// transitionToRoute errors with undefined...
this.controller.transitionToRoute('user', user);
//OR this.transitionTo('user', user);
}.bind(this), function() {
alert('failed to update user!');
});
}
2) store router instance in a variable outside 'then' so its accessible within the then block
update: function(user) {
// Extra Code that checks current_password and others omitted.
var context = this;
user.save().then(function() {
alert('User Updated!');
// transitionToRoute errors with undefined...
context.controller.transitionToRoute('user', user);
}, function() {
alert('failed to update user!');
});
}
3) Or use ES6 arrow function which will bind this context automagically. I have not tried these before, but it should work.
user.save().then(()=> {
alert('User Updated!');
this.controller.transitionToRoute('user', user);
}, () => {
alert('failed to update user!');
});
Hope this helps.
I am very new to ember, but I've spent hours with this problem and can't solve it on my own. Here's my route (using ember-cli):
import Ember from 'ember';
export default Ember.Route.extend({
actions: {
save: function() {
var controller = this.controller;
controller.get('model').save().then(function(account) {
console.log('account saved');
controller.transitionToRoute('accounts.index');
}, function(response) {
console.log('account NOT saved');
});
return false;
},
deleteAccount: function() {
var controller = this.controller;
controller.get('model').destroyRecord().then(function(account) {
console.log('account deleted');
controller.transitionToRoute('accounts.index');
}, function(response) {
console.log('account NOT deleted');
});
return false;
},
cancel: function() {
this.controller.get('model').rollback();
this.transitionToRoute('accounts.index');
return false;
},
}
});
I am triggering the deleteAccount action in my template (button). The interesting thing is that the code is actually deleting the record. It sends a successful delete request and the api deletes the account. But it never transitions to accounts.index. Instead it logs "account NOT deleted". If I manually go to account.index then the model isn't there any more (as one would expect).
I got the code from the official ember docs. See: http://emberjs.com/api/data/classes/DS.Model.html#method_destroyRecord
So why is the promise always failing when the model is actually deleted? Your help would be very much appreciated!
Btw.: It is an edit route with account_id passed as param, so no need for manually defining a "model function" on the route. Just in case someone was wondering.
I guess I've just solved it. The reason for the failing of the destroyRecord() promise seemed to be that my API responded with an EMPTY HTTP 200 response. But 200 usually implies that an entity is returned which isn't the case. So I adapted the API to return an empty 204 response and this did the trick. This SO answer actually helped a lot: HTTP status code for update and delete?
A successful response SHOULD be 200 (OK) if the response includes an
entity describing the status, 202 (Accepted) if the action has not yet
been enacted, or 204 (No Content) if the action has been enacted but
the response does not include an entity.
I'm trying to test my registration and login processes, the integration tests were passing perfectly prior to creating an initializer to extend the Ember-Simple-Auth Session object with the currentUser property.
It all works correctly in the browser, its just the tests now fail all in the sessionAuthenticationSucceeded action in the application route on the following line:
this.get('session.currentUser').then(function(user) {
with : TypeError: Cannot read property 'then' of undefined
/routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
actions: {
sessionAuthenticationSucceeded: function () {
var self = this;
this.get('session.currentUser').then(function(user) {
if (user.get('account') && user.get('status') === 'complete'){
self.transtionTo('home');
} else {
console.log('Need to complete Registration');
self.transitionTo('me');
}
});
}
}
}
/initializers/custom-session.js
import Ember from 'ember';
import Session from 'simple-auth/session';
export default {
name: 'custom-session',
before: 'simple-auth',
initialize: function(container) {
// application.deferReadiness();
Session.reopen({
currentUser: function() {
var id = this.get('user_id');
if (!Ember.isEmpty(id)) {
console.log('getting the current user');
return container.lookup('store:main').find('user', id);
}
}.property('user_id')
});
// application.advanceReadiness();
}
};
/tests/integration/visitor-signs-up-test.js
test('As a user with valid email and password', function(){
var email = faker.internet.email();
signUpUser(email, 'correctpassword', 'correctpassword');
andThen(function(){
equal(find('#logged-in-user').text(), email, 'User registered successfully as ' + email);
equal(sessionIsAuthenticated(App), true, 'The session is Authenticated');
});
});
test/helpers/registration-login.js
export function signUpUser(email, password, passwordConfirmation) {
visit('/register').then(function(){
fillIn('input.email', email);
fillIn('input.password', password);
fillIn('input.password-confirmation', passwordConfirmation);
click('button.submit');
});
}
I have tried using
application.deferReadiness()
as you can see commented out in the initializer (also pass in application in that instance) to ensure the async request has completed and user is available but that hasn't worked either.
I am using Pretender to intercept the api requests, but the call to api/v1/users/:id isn't being made at all during the tests.
The strange part is it works perfectly in the browser.
I'm trying to understand why this won't this work? Any guidance would be appreciated!
NB: I have also tried solution listed here and here with same outcome as above.
I have figured out the problem, turns out I wasn't returning a user_id from the api/v1/users/sign_in request Pretender was intercepting hence when sessionAuthenticationSucceeded fired, there was no user_id available and thus currentUser was never being updated/triggered.
I'll leave all the code up there in case it helps somebody else. Comments or improvements to it are still very welcome!
I'm simply trying to write some tests to make sure logging in and out works, including everything that goes with it. Here's what I'm doing so far:
tests/integration/sessions-test.js
import Ember from "ember";
import { test } from 'ember-qunit';
import startApp from '../helpers/start-app';
var App;
module('Integrations: Sessions', {
setup: function() {
App = startApp();
},
teardown: function() {
Ember.run(App, App.destroy);
}
});
test('Unsuccessful Sign In', function() {
expect(3);
visit('/sign-in');
andThen(function() {
fillIn('input#email', 'test#user.com');
fillIn('input#password', 'bad_password');
click('input#submit');
andThen(function() {
equal(currentRouteName(), 'sign-in', 'Unsuccessfull sign in stays on the sign in page.');
ok($('input#email, input#password').hasClass('error'), 'Inputs have a class of "error."');
equal($('input#submit').prop('disabled'), false, 'Submit button is not disabled.');
});
});
});
test('Successful Sign In', function() {
expect(2);
visit('/sign-in');
andThen(function() {
fillIn('input#email', 'test#user.com');
fillIn('input#password', 'password');
click('input#submit');
andThen(function() {
equal(currentRouteName(), 'welcome', 'Successfull sign in redirects to welcome route.');
ok(find('.message').length, "Page contains a list of messages.");
});
});
});
And, here's a trimmed down version of the sign in logic behind the scenes:
app/controllers/sign-in.js
import Ember from 'ember';
export default Ember.Controller.extend({
needs: ['application'],
actions: {
signIn: function() {
var self = this;
var data = this.getProperties('email', 'password');
// Attempt to sign in and handle the response.
var promise = Ember.$.post('/v3/sessions', data);
promise.done(function(response) {
Ember.run(function() {
self.get('controllers.application').set('token', response.access_token);
self.transitionToRoute('welcome');
});
});
...
}
}
});
The "Unsuccessful Sign In" test works just fine. The "Successful Sign In" starts to work, then quits halfway through. It signs in, then redirects correctly. On the welcome page, when it makes a call to get the messages, the node server is responding with Error: Not enough or too many segments and a 500 status. What in the world does that mean and how can I fix it, assuming I don't have any control over the API?
Also, the API is written primarily using Koa and Passport, as far as I know.
Figured it out. Apparently, it was an authentication error, not that you'd ever be able to guess that by the error message.
In the sign in controller, there's a line where I was setting the token property of the application controller. The application controller had an observer to watch that property for changes, then setup the AJAX headers when it changed. Problem is, observes use Ember's run loop, which is disabled while testing.
To fix the issue, I set the AJAX headers there in the sign in controller, just before transitioning to the welcome route.
I am trying to test an ember component with mocha and sinon. I wanted to test one of the actions of the component which makes an ajax call by using sinon's "useFakeXMLHttpRequest". But this test is causing time-out error. I am using mocha test adapter for ember taken from https://github.com/teddyzeenny/ember-mocha-adapter, I couldn't find the js file in cloud so I have pasted in whole code - so it might look bit messy in the jsbin.
Here is a jsbin link to the issue : http://jsbin.com/usajOhE/1/
The code for the component is :
AS.QuestionViewComponent = Ember.Component.extend({
templateName: "components/question-view",
actions: {
makeAjaxCall: function() {
jQuery.ajax({
url: "/todo/items",
success: function(data) {
//callback(null, data);
}
});
}
}
});
The handle bar associated with the component is :
<a {{action "makeAjaxCall"}} class="test-link">Make ajax call</a>
And my test script is:
describe("Testing", function() {
var xhr, requests;
before(function() {
xhr = sinon.useFakeXMLHttpRequest();
requests = [];
xhr.onCreate = function(req) {
requests.push(req);
};
});
after(function() {
xhr.restore();
});
beforeEach(function() {
AS.reset();
visit("/");
});
it("shoud make ajax call", function() {
//TIMESOUT HERE
click($("a.test-link:first")).then(function() {
console.log(requests);
expect(requests.length).to.be(1);
});
});
});
Your help will be much appreciated. Thanks
Most likely it is because you have not responded to the fake ajax request. The ember-testing package counts the pending ajax requests made by jQuery (see pendingAjaxRequests here). If this stays at 1, the ember-testing wait() helper never resolves.
The ember-testing package increments this counter via ajaxStart and ajaxStop filters.
To clarify what's happening here: When you use the click() helper, this sends a click message to the element, and then defers to the wait() helper (a promise). The same applies for other helpers such as fillIn(), keyEvent() etc. You can see from the comments in the source for wait() that it will not progress on with the rest of your specs:
// 1. If the router is loading
// 2. *If there are pending Ajax requests
// 3. If there are scheduled timers or we are inside of a run loop
The fix:
Unfortunately, if you never make it to the then block of your test, you cannot fake a response via requests[0].respond(...).
Instead, I've solved this by using sinon's fake server:
var server;
beforeEach(function () {
server = sinon.fakeServer.create();
server.autoRespond = true;
server.autoRespondAfter = 1; // ms
App.reset();
});
afterEach(function () {
server.restore();
});
it("should make ajax call", function() {
// set up the fake response
server.responses[0].response = [200, { "Content-Type": "application/json" }, '{ "todos": [] }'];
visit('/')
.click($("a.test-link:first"))
.then(function() {
// should make it to here now
});
});
This pattern works fine when you are expecting a single, or a deterministic order of ajax requests going into your fake server. If you expect lots of requests (with different paths), you can use server.respondWith([regex], ...) to match certain urls to specific responses.
Another thing to note is that it's generally good practice to put the success part of your ajax call into an Ember.run:
jQuery.ajax({
url: "/todo/items",
success: function(data) {
Ember.run(function () {
//callback(null, data);
})
}
});