Testing Ember-data w/Jasmine and DS.FixtureAdapter - ember.js

I'm trying to do a Jasmine test of ember-data (using the current master) using the DS.FixtureAdapter. I've tried dozens of variations on the below code (with and without trying to create an Application namespace). I've also gone into the ember-data source to try and see what's going on, as well as referenced the tests in ember-data itself as an example.
I've also tried variations of Person.find(1), using Ember.run blocks and Jasmine wait()'s.
Whatever I try, store.find(Person, 'test') returns a result but attempting to get one of the attributes results in null (test assertion fails). What is it I'm not seeing? Thanks for any help!
describe "a test", ->
store = null
Person = null
beforeEach ->
store = DS.Store.create
revision: 11
adapter: 'DS.FixtureAdapter'
Person = DS.Model.extend
firstName: DS.attr('string')
lastName: DS.attr('string')
age: DS.attr('number')
it "works or does it", ->
Person.FIXTURES = [{
id: 'test'
firstName: 'Kyle'
lastName: 'Stevens'
age: 30
}]
kyle = store.find(Person, 'test')
expect(Em.get(kyle, 'firstName')).toEqual('Kyle')

Whatever I try, store.find(Person, 'test') returns a result but attempting to get one of the attributes results in null (test assertion fails). What is it I'm not seeing? Thanks for any help!
This is a timing issue. When you call store.find() it runs query asynchronously and returns a model promise. That means the query is still running (or scheduled to run) when control returns to your test, resulting in a failed expectation.
This is what we love about ember, it means your app can treat kyle as if the data were present and trust that values will be updated automagically via bindings when the data becomes available.
Of course all this magic is not so great when it is preventing your test from passing. Here are some alternative approaches:
1) Register a didLoad callback
kyle = store.find(Person, 'test');
kyle.on('didLoad', function() {
console.log('should = kyle: ', Em.get(kyle, 'firstName'));
});
2) Instead of didLoad could use more blackbox testing approach and just verify that the name is set propertly within 100 ms of having called find - of course this can lead to brittle tests
Ember.run.later(this, function() {
console.log('should = kyle: ', Em.get(kyle, 'firstName'));
console.log('should = kim: ', Em.get(App.kim, 'firstName'));
}, 100);
I believe that in a jasmine test you could wrap your setup code in a runs() method and use waitsFor to verify that the value has been set as expected:
waitsFor(function() {
return Em.get(kyle, 'firstName') == 'Kyle';
}, "x to be set to 5", 100);
See this JSBIN for working (non-jasmine) example:
http://jsbin.com/apurac/4/edit
See this post for tips on async testing with jasmine: http://blog.caplin.com/2012/01/17/testing-asynchronous-javascript-with-jasmine/
Also, be sure to set Ember.testing = true for all of your tests. See this SO post for detail: Is it recommended to set Ember.testing = true for unit tests?

Related

Ember-CLI-Mirage enforcing JSON:API?

Stumped on a couple failures and want to know if I'm understanding Mirage correctly:
1.In ember-cli-mirage, am I correct that the server response I define should reflect what my actual server is returning? For example:
this.get('/athletes', function(db, request) {
let athletes = db.athletes || [];
return {
athletes: athletes,
meta: { count: athletes.length }
}
});
I am using custom serializers and the above matches the format of my server response for a get request on this route, however, on two tests I'm getting two failures with this error: normalizeResponse must return a valid JSON API document: meta must be an object
2.Is mirage enforcing the json:api format, and is it doing so because of the way I'm setting up the tests?
For example, I have several tests that visit the above /athletes route, yet my failures occur when I use an async call like below. I would love to know the appropriate way to correctly overwrite the server response behavior, as well as why the normalizeResponse error appears in the console for 2 tests but only causes the one below to fail.
test('contact params not sent with request after clicking .showglobal', function(assert) {
assert.expect(2);
let done = assert.async();
server.createList('athlete', 10);
//perform a search, which shows all 10 athletes
visit('/athletes');
fillIn('.search-inner input', "c");
andThen(() => {
server.get('/athletes', (db, request) => {
assert.notOk(params.hasOwnProperty("contacts"));
done();
});
//get global athletes, which I thought would now be intercepted by the server.get call defined within the andThen block
click('button.showglobal');
});
});
Result:
✘ Error: Assertion Failed: normalizeResponse must return a valid JSON API document:
* meta must be an object
expected true
I tried changing my server response to a json:api format as suggested in the last example here but this looks nothing like my actual server response and causes my tests to fail since my app doesn't parse a payload with this structure. Any tips or advice must appreciated.
You are correct. Are the failures happening for the mock you've shown above? It looks to me like that would always return meta as an object, so verify the response is what you think it should be by looking in the console after the request is made.
If you'd like to see responses during a test, enter server.logging = true in your test:
test('I can view the photos', function() {
server.logging = true;
server.createList('photo', 10);
visit('/');
andThen(function() {
equal( find('img').length, 10 );
});
});
No, Mirage is agnostic about your particular backend, though it does come with some defaults. Again I would try enabling server.logging here to debug your tests.
Also, when writing asserts against the mock server, define the route handlers at the beginning of the test, as shown in the example from the docs.
I was able to get my second test to pass based on Sam's advice. My confusion was how to assert against the request params for a route that I have to visit and perform actions on. I was having to visit /athletes, click on different buttons, and each of these actions was sending separate requests (and params) to the /athletes route. That's is why I was trying to redefine the route handler within the andThen block (i.e. after I had already visited the route using the route definition in my mirage/config file).
Not in love with my solution, but the way I handled it was to move my assertion out of route handler and instead assign the value of the request to a top-level variable. That way, in my final andThen() block, I was able to assert against the last call to the /athletes route.
assert.expect(1);
//will get assigned the value of 'request' on each server call
let athletesRequest;
//override server response defined in mirage/config in order to
//capture and assert against request/response after user actions
server.get('athletes', (db, request) => {
let athletes = db.athletes || [];
athletesRequest = request;
return {
athletes: athletes,
meta: { count: athletes.length }
};
});
//sends request to /athletes
visit('/athletes');
andThen(() => {
//sends request to /athletes
fillIn('.search-inner input', "ab");
andThen(function() {
//sends (final) request to /athletes
click('button.search');
andThen(function() {
//asserts against /athletes request made on click('button.search') assert.notOk(athletesRequest.queryParams.hasOwnProperty("contact"));
});
});
});
I'm still getting console errors related to meta is not an object, but they are not preventing tests from passing. Using the server.logging = true allowed me to see that meta is indeed an object in all FakeServer responses.
Thanks again to Sam for the advice. server.logging = true and pauseTest() make acceptance tests a lot easier to troubleshoot.

Testing: how to assert between two sequential promises/run.laters? How to skip `run.later` waiting in tests?

Here's a simple component:
App.FooBarComponent = Ember.Component.extend({
tagName: "button",
status: "Ready",
revertStatusPeriodMs: 2000,
click: function() {
this.set('status', 'Pending');
// A fake ajax
this.run()
.then( function() {
this.updateStatus('Finished');
}.bind(this))
.catch( function() {
this.updateStatus('Error');
}.bind(this));
},
run: function() {
return new Ember.RSVP.Promise( function(resolve) {
Ember.run.later( function() {
resolve();
}, 500);
});
},
updateStatus: function(statusText) {
this.set('status', statusText);
var periodMs = this.get('revertStatusPeriodMs') || 1000;
Ember.run.later( function() {
this.set('status', 'Ready');
}.bind(this), periodMs);
}
});
It does a simple thing: when clicked, it displays some text. Later replaces the text with another one. Even later, it reverts the text to the initial one.
The code works fine. My problem is that i'm unable to write a test for it.
test('clicking the button should set the label', function(assert) {
expect(4);
visit('/');
assert.equal( find('button').text().trim(), 'Ready', 'Should initially be "Ready"');
andThen(function() {
click('button');
assert.equal( find('button').text().trim(), 'Pending', 'Should "Pending" right after click');
});
// This one fires too late!
andThen(function() {
assert.equal( find('button').text().trim(), 'Finished', 'Should become "Finished" after promise fulfills');
});
andThen(function() {
assert.equal( find('button').text().trim(), 'Ready', 'Should eventually return to the "Ready" state');
});
});
I have two problems:
I'm unable to test the Finished state. It seems that andThen waits for all promises and run.laters to finish, while i want to test an intermediate state. How do i run an assertion between two sequential promises/run.laters?
Times can be long, and tests can take forever to complete. I generally don't mind refactoring the code for better testability, but i refuse to adjust times in the app based on the environment (e. g. 2000 for dev/prod, 0 for test). Instead, i would like to use a timer mock or some other solution.
I've tried Sinon and failed: when i mock the timer, andThen never returns. Neither of these solutions helped me.
JSBin: http://emberjs.jsbin.com/fohava/2/edit?html,js,output (Sinon is included)
None of your test code seems to use asynch – which would seem to be crucial since without anything to distinguish them, the last 2 tests will execute in the same tick (tests 3 & 4 can't both be true at the same time). I'm not familiar with QUnit, but I did find it's async method, which would seem to be pertinent. However when I tried calling assert.async, it blew up. I tried updating QUnit 1.17, still no dice.
So I don't have a solution for you. The problem, though, is that Ember will only execute both andThen tests once all the asynchronous execution has finished – this means that test 3 will never be true.
I did some code where I basically got it working but it seems really finicky and not something you would want to be do be doing..
I did this a few days ago but never replied because I wasn't happy with it - it doesn't give you enough control..
The reason I'm replying now however is because I've found a PR that may be of use to you: PR 10463
I didn't look into too much detail but apparently it "allows custom async test helpers to pause execution by returning a promise"
I'm not sure what version of Ember you're using - but if you can hold out for this PR it may help you..

Ember - Issue with HTTP POST request

I have written a (very) simple RESTFul Web service to retrieve data from MongoDB using Node, Express and Mongoose.
On the server side, I have this code:
router.route('/products').post(function(req,res){
var product = new Product(req.body);
product.save(function(err){
if(err)
res.send(err);
res.send({message:'Product Added'});
});
When I submit a request from my Ember client, the req.body contains something like the following:
{ attributes:
{ category: 1,
name: 'y',
price: 1,
active: false,
notes: null } }
The attribute names are exactly the same as my mongoose schema. I get no error but the document created in MongoDB is empty (just get the _id and __v fields).
What am I doing wrong. Should I convert the req.body further into ???
A couple things that will help debug:
1) From a quick glance (I haven't used mongoose before) it looks like call back function passed to save takes two arguments.
2) I don't know if your code got cut off, but the sample above was missing a matching });
3) I made the function short circuit itself on error, so you will not see 'Product added' unless that is truly the case.
Try these fixes.
router.route('/products').post(function(req,res){
var product = new Product(req.body);
product.save(function(err, product){
if(err){
return res.send(err);
}
return res.send({message:'Product Added'});
});
});
The issue was related to my lack of familiarity with Ember and Node+Express. The data received in the server is slightly different from what I had first indicated: (first line was missing)
{ product:
{ attributes:
{ category: ... } } }
On the server side I can access my data using req.body.product.attributes (instead of req.body):
router.route('/products').post(function(req,res){
var product = new Product(req.body.product.attributes);
product.save(function(err){
if(err)
res.send(err);
res.send({message:'Product Added'});
});

Ember CLI Controller Test: Uncaught TypeError: Cannot read property 'transitionToRoute' of null

I have a controller I'm testing with Ember CLI, but the controller's promise will not resolve, as the controller's transitionToRoute method is returning null:
Uncaught TypeError: Cannot read property 'transitionToRoute' of null
login.coffee
success: (response) ->
# ...
attemptedTransition = #get("attemptedTransition")
if attemptedTransition
attemptedTransition.retry()
#set "attemptedTransition", null
else
#transitionToRoute "dashboard"
login-test.coffee
`import {test, moduleFor} from "ember-qunit"`
moduleFor "controller:login", "LoginController", {
}
# Replace this with your real tests.
test "it exists", ->
controller = #subject()
ok controller
###
Test whether the authentication token is passed back in JSON response, with `token`
###
test "obtains authentication token", ->
expect 2
workingLogin = {
username: "user#pass.com",
password: "pass"
}
controller = #subject()
Ember.run(->
controller.setProperties({
username: "user#pass.com",
password: "pass"
})
controller.login().then(->
token = controller.get("token")
ok(controller.get("token") isnt null)
equal(controller.get("token").length, 64)
)
)
When the line #transitionToRoute("dashboard") is removed, the test passes; otherwise, the test fails.
How can I fix this error, while still maintaining my controller logic?
Work around: bypass transitionToRoute if target is null. Something like:
if (this.get('target')) {
this.transitionToRoute("dashboard");
}
I ran into the same error and dug into Ember source code a little bit. In my case this error is thrown by ControllerMixin because get(this, 'target') is null at this line. The test module probably has no idea what target should be in a controller unit test like this without further context, so you may need to manually set it or just bypass it.
Since you're not interested in the transition itself, you can just stub out the transitionToRoute method on the controller.
JS:
test('Name', function() {
var controller = this.subject();
controller.transitionToRoute = Ember.K;
...
}
Coffee:
test "it exists", ->
controller = #subject()
controller.transitionToRoute = Ember.K
ok controller
Not sure why transitionToRoute method is undefined when you execute it within an unit test - it is probably related to the fact that the execution context is different.
One possible workaround to this would be if you move your transitionToRoute call to the route instead of it being in the controller. That way your controller will send action to its route and you'll keep routing only in the route.
There is a big discussion around which is better practice - routing from controller or not but this is another story.

Ember.js - login example: Ember.run.later

I copied this login example for my own needs. It runs fine. But I am asking myself: why do I need the Ember.run.later(this, this._serverLogin, 100); line? Like the comment said it is only for simulating the delay. Ok. But if I change it to this:
// Create the login controller
MyApp.loginController = Ember.Object.create({
username: '',
password: '',
isError: false,
tryLogin: function() {
if(this.get('username') === MyApp.USERNAME &&
this.get('password') === MyApp.PASSWORD) {
this.set('isError', false);
this.set('username', '');
this.set('password', '');
MyApp.stateManager.send('loginSuccess');
} else {
this.set('isError', true);
MyApp.stateManager.send('loginFail');
}
},
});
without Ember.run.later(this, this._serverLogin, 100);, I get Uncaught Error: <Ember.StateManager:ember270> could not respond to event loginSuccess in state loggedOut.awaitingCredentials. So I thought probably I need this delay to get the stateManager changed before or somethign like that. But when I run the old code with Ember.run.later(this, this._serverLogin, 0); it still works. So, whats different? The documentation of ember didnt gave any hints.
It's because your StateManager is still in the early state setup process when calling a sendEvent (loginSuccess/loginFailed).
By delaying the event sending w/ Ember.run.later, your code is processed in a next run loop, and the state is properly setup.
That being said, you are using Ember in a very old fashion. You should have a look at the state-of-the-art way to manage app routes.