Testing Ember Data - Switching to FixtureAdapter during test runs - unit-testing

I have an ember.js app and I'm setting up a DS.Store like this (view actual code):
(function (app) {
'use strict';
...
var store = DS.Store.extend({
revision: 12
});
app.Store = store;
})(window.Balanced);
Now I have a qunit test and in that test I would like to swap the default RESTAdapter for a FixtureAdapter so that I can setup fixtures for my models. I figure I need to write something like this but I'm not 100% sure:
(function () {
'use strict';
var fixtureAdapter;
module('tests.store.module', {
setup: function () {
fixtureAdapter = DS.FixtureAdapter.extend({
});
Balanced.Store.reopen({
adapter: fixtureAdapter
});
// TODO: how does this work?
Balanced.Marketplace.FIXTURES = [
{id: 1, name: '1'},
{id: 2, name: 'poop'},
{id: 3, name: 'poop'}
];
},
teardown: function () {
// teardown code
}
}
);
test("Marketplace query", function () {
var marketplaces = Balanced.Marketplace.find();
// TODO: how do I test this?
});
})();

For my basic unit testing with jasmine I setup the store manually like so (using the local storage adapter to avoid xhr requests)
describe ("CodeCamp.SessionView Tests", function(){
var get = Ember.get, set = Ember.set, sut, controller, session, store;
beforeEach(function(){
store = DS.Store.create({
revision: 11,
adapter: DS.LSAdapter.create()
});
sut = CodeCamp.SessionView.create();
controller = CodeCamp.SessionController.create();
controller.set("store", store);
sut.set("controller", controller);
session = CodeCamp.Session.createRecord({ id: 1, name: "First", room: "A", ratings: [], speakers: [], tags: []});
});
afterEach(function() {
Ember.run(function() {
store.destroy();
controller.destroy();
sut.destroy();
session.destroy();
});
store = null;
controller = null;
sut = null;
session = null;
});
it ("will create rating when form is valid", function(){
sut.set('score', '1234');
sut.set('feedback', 'abcd');
sut.addRating(session);
var ratings = CodeCamp.Session.find(1).get('ratings');
var rating = ratings.objectAt(0);
expect(rating.get('score')).toEqual('1234');
expect(rating.get('feedback')).toEqual('abcd');
expect(rating.get('session').get('id')).toEqual(1);
});
});
The test above goes end-to-end for the following ember view
CodeCamp.SessionView = Ember.View.extend({
templateName: 'session',
addRating: function(event) {
if (this.formIsValid()) {
var rating = this.buildRatingFromInputs(event);
this.get('controller').addRating(rating);
this.resetForm();
}
},
buildRatingFromInputs: function(session) {
var score = this.get('score');
var feedback = this.get('feedback');
return CodeCamp.Rating.createRecord(
{ score: score,
feedback: feedback,
session: session
});
},
formIsValid: function() {
var score = this.get('score');
var feedback = this.get('feedback');
if (score === undefined || feedback === undefined || score.trim() === "" || feedback.trim() === "") {
return false;
}
return true;
},
resetForm: function() {
this.set('score', '');
this.set('feedback', '');
}
});
If you want to see this entire app in action (just a sample ember app with a few basic jasmine tests) it's on github
https://github.com/toranb/ember-code-camp/

Related

Acceptance tests aren't resetting

I have some acceptance tests that test a component. If I run each test separately, they pass just fine. However, when I run the tests together, they fail because they're retaining the values from the previous tests.
Here is my code:
filter-test.js
module('Integration - Filter', {
beforeEach: function() {
App = startApp();
server = setupPretender();
authenticateSession();
},
afterEach: function() {
Ember.run(App, 'destroy');
server.shutdown();
}
});
test('filters can be saved and selected via the dropdown', function(assert) {
visit('/status');
fillIn('.filter-status', 'Not Completed');
fillIn('.filter-id', '444');
andThen(function() {
assert.ok(find('.status-title').text().includes('2 of 7'), 'the new filter filters the results');
});
});
test('only saved filters can be edited', function(assert) {
visit('/status');
fillIn('.filter-id', 'not an id');
click('.update-filter');
andThen(function() {
assert.equal(find('.alert').text(), 'Not a Saved Filter×');
});
});
test('filter values can be cleared', function(assert) {
visit('/status');
fillIn('.filter-id', '444');
fillIn('.filter-status', 'Completed');
click('.clear-filters');
andThen(function() {
// this fails because `.filter-id` is set to 'not an id':
assert.equal(find('.filter-id').val(), '', 'filter for was reset to its initial value');
// this also fails because `.filter-status` is set to 'Not Completed':
assert.equal(find('.filter-status').val(), 'Everything', 'status dropdown was reset to its initial value');
});
});
ps-filter/component.js
export default Ember.Component.extend({
classNames: ['panel', 'panel-default', 'filter-panel'],
currentFilter: null,
initialValues: null,
didInsertElement: function() {
this.set('initialValues', Ember.copy(this.get('filterValues')));
},
actions: {
saveFilter: function(name) {
var filters = this._getFilterList();
var filterValues = this.get('filterValues');
if (!Ember.isEmpty(name)) {
filters[name] = filterValues;
this.sendAction('updateFilter', filters);
this.set('currentFilter', name);
}
},
updateFilter: function() {
var filterValues = this.get('filterValues');
var currentFilter = this.get('currentFilter')
var filters = this.get('userFilters');
filters[currentFilter] = filterValues;
this.sendAction('updateFilter', filters);
},
clearFilters: function() {
this.set('currentFilter', null);
this.set('filterValues', Ember.copy(this.get('initialValues')));
}
}
});
status/controller.js
export default Ember.ArrayController.extend({
filterValues: {
filterStatus: 'Everything',
filterId: 'id',
},
userFilters: Ember.computed.alias('currentUser.content.preferences.filters')
});
status/template.hbs
<div class="row">
{{ps-filter
filterValues=filterValues
userFilters=userFilters
updateFilter='updateFilter'
}}
</div>
From what I gathered, it seems that it sets the initialValues to the filterValues left over from the previous test. However, I thought that the afterEach was supposed to reset it to its original state. Is there a reason why it doesn't reset it to the values in the controller?
Note that the component works normally when I run it in development.
Ember versions listed in the Ember Inspector:
Ember : 1.11.3
Ember Data : 1.0.0-beta.18
I'm running Ember CLI 0.2.7.
Edit
I don't think this is the issue at all, but here is my pretender setup:
tests/helpers/setup-pretender.js
export default function setupPretender(attrs) {
var users = [
{
id: 1,
name: 'ttest',
preferences: null
}
];
var activities = [
{
id: 36874,
activity_identifier: '18291',
status: 'Complete'
}, {
id: 36873,
activity_identifier: '82012',
status: 'In Progress'
}, {
id: 35847,
activity_identifier: '189190',
status: 'In Progress'
}, {
id: 35858,
activity_identifier: '189076',
status: 'Not Started'
}, {
id: 382901,
activity_identifier: '182730',
status: 'Not Started'
}, {
id: 400293,
activity_identifier: '88392',
status: 'Complete'
}, {
id: 400402,
activity_identifier: '88547',
status: 'Complete'
}
];
return new Pretender(function() {
this.get('api/v1/users/:id', function(request) {
var user = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
return [200, {"Content-Type": "application/json"}, JSON.stringify({user: user})];
});
this.get('api/v1/activities', function(request) {
return [200, {"Content-Type": "application/json"}, JSON.stringify({
activities: activities
})];
});
this.put('api/v1/users/:id', function(request) {
var response = Ember.$.parseJSON(request.requestBody);
response.user.id = parseInt(request.params.id, 10);
var oldUser = users.find(function(user) {
if (user.id === parseInt(request.params.id, 10)) {
return user;
}
});
var oldUserIndex = users.indexOf(oldUser);
if (oldUserIndex > -1) {
users.splice(oldUserIndex, 1);
users.push(response.user);
}
return [200, {"Content-Type": "application/json"}, JSON.stringify(response)];
});
});
}
When I run the tests, it fails because it reset the value to the one in the previous test. For example, when I run 'filter values can be cleared', the .filter-id input has the same .filter-id value from 'only saved filter can be edited. If I change the value in 'only saved filters can be edited'back to '', the 'filter values can be cleared' test passes.
Basically, the component sets the initialValues property when it first inserts the element. It's set to a copy of the filterValues property, so it should be set to the controller's filterValues property, and shouldn't change. However, it seems that the modified filterValues property is carried over to the next test, which means that initialValues is set to that modified property when it rerenders. So, the test rerenders the templates, but retains the modified values in the controller and component.
I can make the tests pass by creating an initialValues property in the controller and passing that into the component, but that'd mean having duplicate properties in the controller (since filterValues and initialValues would have the same values).
I could modify the user record in the component, but I thought we're supposed to only modify records in the controller or router. Besides, isn't the afterEach hook supposed to reset the app?

model returns null on controller

i'm working with a a router and a controller, and i need to complete some operations on the controller, this is my model code
AcornsTest.StockRoute = Ember.Route.extend({
model: function(params) {
"use strict";
var url_params = params.slug.split('|'),
url = AcornsTest.Config.quandl.URL + '/' + url_params[0] + '/' + url_params[1] + '.json',
stockInStore = this.store.getById('stock', url_params[1]),
today = new Date(),
yearAgo = new Date(),
self = this;
yearAgo.setFullYear(today.getFullYear() - 1);
today = today.getFullYear()+'-'+today.getMonth()+'-'+today.getDate();
yearAgo = yearAgo.getFullYear()+'-'+yearAgo.getMonth()+'-'+yearAgo.getDate();
if(stockInStore && stockInStore.get('data').length) {
return stockInStore;
}
return Ember.$.getJSON(url,{ trim_start: yearAgo, trim_end: today, auth_token: AcornsTest.Config.quandl.APIKEY })
.then(function(data) {
if(stockInStore) {
return stockInStore.set('data', data.data);
} else {
return self.store.createRecord('stock', {
id: data.code,
source_code: data.source_code,
code: data.code,
name: data.name,
description: data.description,
display_url: data.display_url,
source_name: data.source_name,
data: data.data,
slug: data.source_code+'|'+data.code
});
}
});
}
});
and this is my controller
AcornsTest.StockController = Ember.ObjectController.extend({
init: function() {
"use strict";
this.send('generateChartInfo');
},
actions: {
generateChartInfo: function() {
"use strict";
console.log(this.model);
console.log(this.get('model'));
}
}
});
from the controller i'm trying to get access to the model to get some information and format it, and send it to the view
but this.model or this.get('model') always returns null, how can i successful get access to the model from the controller? thanks
You are overriding the init method, but its broken, do this:
AcornsTest.StockController = Ember.ObjectController.extend({
init: function() {
"use strict";
this._super();
this.send('generateChartInfo');
});
You need to call the parent method.
See this test case: http://emberjs.jsbin.com/gijon/3/edit?js,console,output
The model is not ready at init time. If anyone has official docs please share.

angularJS testing factories, returning undefined after get() - async issue

I am new to both angular and TDD and I'm looking for some help with one of my tests. The application I am making talks to an API backend, I have mocked this backend with some js fixtures.
json data:
{
"count": 25,
"total_count": 32,
"current_page": 1,
"per_page": 25,
"pages": 2,
"products": [
{"name": "test"},
{},
{},
{},
]}
Each of the products have a number of data I am only outlining the structure above.
My Passing test (jasmine)
'use strict';
describe('productsController', function() {
var scope, $httpBackend;
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(angular.mock.inject( function($rootScope, $controller, _$httpBackend_) {
//Actual API 0.0.0.0:3000/api/, below is mock data only.
var api_root = '0.0.0.0:3000/api/'
$httpBackend = _$httpBackend_;
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
scope = $rootScope.$new();
$controller('productsController', {$scope: scope});
}));
//Start Tests
it('Should fetch all products', function() {
// $httpBackend.flush();
// expect(scope.products[5].name).toBe('Ruby on Rails Baseball Jersey');
$httpBackend.flush();
expect(scope.products.count).toBe(25);
expect(scope.products.products[0].id).toBe(1);
expect(scope.products.products[0].name).toBe('Ruby on Rails Tote');
expect(scope.products.products[9].id).toBe(5);
expect(scope.products.products[9].permalink).toBe('ruby-on-rails-ringer-t-shirt');
});
});
controller:
// Generated by CoffeeScript 1.6.3
(function() {
var sprangularServices;
sprangularServices = angular.module('sprangularServices', ['ngResource']);
sprangularServices.factory('Defaults', function() {
return {
api_url: "0.0.0.0:3000/api/"
};
});
sprangularServices.factory('Product', function($resource, Defaults) {
var Product;
return Product = (function() {
function Product() {
this.service = $resource(Defaults.api_url + 'products/:id', {
id: '#id'
});
}
Product.prototype.create = function(attrs) {
new this.service({
product: attrs
}).$save(function(product) {
return attrs.id = product.id;
});
return attrs;
};
Product.all = function() {
var data, service;
service = $resource(Defaults.api_url + 'products');
data = service.get();
};
return Product;
})();
});
}).call(this);
Tests:
'use strict';
describe('productsController', function() {
var scope, $httpBackend;
beforeEach(angular.mock.module('sprangularApp'));
beforeEach(angular.mock.inject( function($rootScope, $controller, _$httpBackend_) {
//Actual API 0.0.0.0:3000/api/, below is mock data only.
var api_root = '0.0.0.0:3000/api/'
$httpBackend = _$httpBackend_;
//Get mock jsons
jasmine.getJSONFixtures().fixturesPath='base/js/tests/api_mock';
$httpBackend.when('GET', api_root + 'products').respond(
getJSONFixture('products.json')
);
scope = $rootScope.$new();
$controller('productsController', {$scope: scope});
}));
//Start Tests
it('Should fetch all products', function() {
// $httpBackend.flush();
// expect(scope.products[5].name).toBe('Ruby on Rails Baseball Jersey');
$httpBackend.flush();
expect(scope.products.count).toBe(25);
expect(scope.products.products[0].id).toBe(1);
expect(scope.products.products[0].name).toBe('Ruby on Rails Tote');
expect(scope.products.products[9].id).toBe(5);
expect(scope.products.products[9].permalink).toBe('ruby-on-rails-ringer-t-shirt');
});
});
The above code works fine and the test passes with flying colors. What I would like to do however is have Product.all return the products array within the JS without the meta information like product count etc.
I modified the service so that it would return:
Product.all = function() {
var data, service;
service = $resource(Defaults.api_url + 'products');
data = service.get();
return data.products
};
It seems data.products is undefined, as is data, I have a feeling this could have something to do with flush, but I am not sure, why doesn't data.products return the array that is contained within the JSON? is it to do with get()'s synchronosity.
I think it is a synchronous issue.
Does this do what you want?
Product.all = function() {
var data, service;
service = $resource(Defaults.api_url + 'products');
return service.get().$promise.then(function (result) {
return result.products;
});
};
EDIT
If all you're doing is putting the result of Product.all on the $scope then you can (untested) do this:
Product.all = function() {
var data, service;
var products = [];
service = $resource(Defaults.api_url + 'products');
service.get().$promise.then(function (result) {
for (var i in result.products) {
products.push(result.products[i]);
}
});
return products;
};
If you wish to do something else I'd do (untested):
$scope.$watch(Product.all, function (value) {
$scope.products = value;
// other stuff here to products
}/* , true */); // You probably need to deeply watch the array for changes - I can't test it at the moment.

How to do Ember integration testing for route transitions?

I'm having a problem doing integration testing with ember using Toran Billup's TDD guide.
I'm using Karma as my test runner with Qunit and Phantom JS.
I'm sure half of if has to do with my beginner's knowledge of the Ember runloop. My question is 2 parts:
1) How do I wrap a vist() test into the run loop properly?
2) How can I test for transitions? The index route ('/') should transition into a resource route called 'projects.index'.
module("Projects Integration Test:", {
setup: function() {
Ember.run(App, App.advanceReadiness);
},
teardown: function() {
App.reset();
}
});
test('Index Route Page', function(){
expect(1);
App.reset();
visit("/").then(function(){
ok(exists("*"), "Found HTML");
});
});
Thanks in advance for any pointers in the right direction.
I just pushed up an example application that does a simple transition when you hit the "/" route using ember.js RC5
https://github.com/toranb/ember-testing-example
The simple "hello world" example looks like this
1.) the template you get redirected to during the transition
<table>
{{#each person in controller}}
<tr>
<td class="name">{{person.fullName}}</td>
<td><input type="submit" class="delete" value="delete" {{action deletePerson person}} /></td>
</tr>
{{/each}}
</table>
2.) the ember.js application code
App = Ember.Application.create();
App.Router.map(function() {
this.resource("other", { path: "/" });
this.resource("people", { path: "/people" });
});
App.OtherRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('people');
}
});
App.PeopleRoute = Ember.Route.extend({
model: function() {
return App.Person.find();
}
});
App.Person = Ember.Object.extend({
firstName: '',
lastName: ''
});
App.Person.reopenClass({
people: [],
find: function() {
var self = this;
$.getJSON('/api/people', function(response) {
response.forEach(function(hash) {
var person = App.Person.create(hash);
Ember.run(self.people, self.people.pushObject, person);
});
}, this);
return this.people;
}
});
3.) the integration test looks like this
module('integration tests', {
setup: function() {
App.reset();
App.Person.people = [];
},
teardown: function() {
$.mockjaxClear();
}
});
test('ajax response with 2 people yields table with 2 rows', function() {
var json = [{firstName: "x", lastName: "y"}, {firstName: "h", lastName: "z"}];
stubEndpointForHttpRequest('/api/people', json);
visit("/").then(function() {
var rows = find("table tr").length;
equal(rows, 2, rows);
});
});
4.) the integration helper I use on most of my ember.js projects
document.write('<div id="foo"><div id="ember-testing"></div></div>');
Ember.testing = true;
App.rootElement = '#ember-testing';
App.setupForTesting();
App.injectTestHelpers();
function exists(selector) {
return !!find(selector).length;
}
function stubEndpointForHttpRequest(url, json) {
$.mockjax({
url: url,
dataType: 'json',
responseText: json
});
}
$.mockjaxSettings.logging = false;
$.mockjaxSettings.responseTime = 0;
I'm unfamiliar with Karma, but the portions of your test that needs to interact with ember should be pushed into the run loop (as you were mentioning)
Ember.run.next(function(){
//do somethin
transition stuff here etc
});
To check the current route you can steal information out of the ember out, here's some information I stole from stack overflow at some point.
var router = App.__container__.lookup("router:main"); //get the main router
var currentHandlerInfos = router.router.currentHandlerInfos; //get all handlers
var activeHandler = currentHandlerInfos[currentHandlerInfos.length - 1]; // get active handler
var activeRoute = activeHandler.handler; // active route
If you start doing controller testing, I wrote up some info on that http://discuss.emberjs.com/t/unit-testing-multiple-controllers-in-emberjs/1865

Backbone.js results of fetch doubling

I am trying to use Django and tastypie with backbone.js and mustache. I have set up an example to study those. When using the below code I get results of users doubling as:
-Id User name
-1 yol
-2 dada
-1 yol
-2 dada
--- MY CODE ---
// I think this tastypie adjustment is not related with the problem but i want you to see the //whole code
window.TastypieModel = Backbone.Model.extend({
base_url: function() {
var temp_url = Backbone.Model.prototype.url.call(this);
return (temp_url.charAt(temp_url.length - 1) == '/' ? temp_url : temp_url+'/');
},
url: function() {
return this.base_url();
}
});
window.TastypieCollection = Backbone.Collection.extend({
parse: function(response) {
this.recent_meta = response.meta || {};
return response.objects || response;
}
});
(function($){
// MODELS-COLLECTIOS
//USERS
var User = TastypieModel.extend({
url: USERS_API
});
var Users = TastypieCollection.extend({
model: User,
url:USERS_API
});
//VIEWS
var UsersView = Backbone.View.extend({
render: function(){
// template with ICanHaz.js (ich)
this.el = ich.userRowTpl(this.model.toJSON());
return this;
}
});
//main app
var AppView = Backbone.View.extend({
tagName: 'tbody',
initialize: function() {
this.users = new Users();
this.users.bind('all', this.render, this);
this.users.fetch();
},
render: function () {
// template with ICanHaz.js (ich)
this.users.each(function (user) {
$(this.el).append(new UsersView({model: user}).render().el);
}, this);
return this;
}
});
var app = new AppView();
$('#app').append(app.render().el);
})(jQuery);
I would say you are firing two times the render, because your view is listening to all events, try instead to bind it just to reset and see how it goes:
initialize: function() {
this.users = new Users();
this.users.bind('reset', this.render, this);
this.users.fetch();
},