Unit test computed property on an Ember controller - unit-testing

The code from my controllers/cart.js:
export default Ember.Controller.extend({
cartTotal: Ember.computed('model.#each.subTotal', function() {
return this.model.reduce(function(subTotal, product) {
var total = subTotal + product.get('subTotal');
return total;
}, 0);
})
)};
This computed property loops over all the elements in the model, adding all the values of the subTotal property, returning a cart total.
cart-test.js
import { moduleFor, test } from 'ember-qunit';
import Ember from 'ember';
moduleFor('controller:cart', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
test('it exists', function(assert) {
var controller = this.subject();
assert.ok(controller);
});
test('cartTotal function exists', function(assert) {
var controller = this.subject();
assert.equal(controller.get('cartTotal'), 30, 'The cart total function exists');
});
The test fails with TypeError: Cannot read property 'reduce' of null because it obviously doesn't have a model to loop over.
How can I mock the dependencies of the cartTotal computed property to make the test pass?
Thanks!

Something along these lines maybe?
import { moduleFor, test } from 'ember-qunit';
import Ember from 'ember';
var products = [
Ember.Object.create({ name: 'shoe', subTotal: 10 }),
Ember.Object.create({ name: 'shirt', subTotal: 20 })];
var model = Ember.ArrayProxy.create({
content: Ember.A(products)
});
moduleFor('controller:cart', {
beforeEach() {
this.controller = this.subject();
}
});
test('cartTotal', function(assert) {
this.controller.set('model', model);
assert.equal(this.controller.get('cartTotal'), 30, 'The cart total function exists');
});

One way to deal with this would be to stub the model in the beforeEach hook:
var sampleModel = [ // sample data that follows your actual model structure ]
moduleFor('controller:cart', {
beforeEach() {
this.controller = this.subject(); // allows you to access it in the tests without having to redefine it each time
this.controller.set('model', sampleModel);
}
});

Related

Ember component not updating in integration test when injected service is updated

I have a side-bar component which relies on side-bar service which is injected into it via initializer.
the component then has a computed property title which is tied to the same property on the service:
title: function () {
return this.get('sideBarService.title');
}.property('sideBarService.title'),
This works in the app itself but I cannot get the component to update in an integration test when the service is upated.
Here is my non working integration test:
import Ember from 'ember';
import startApp from '../helpers/start-app';
import hbs from 'htmlbars-inline-precompile';
import { moduleForComponent, test } from 'ember-qunit';
var application, container, sideBarService;
moduleForComponent('side-bar', 'Integration | side-bar',{
integration: true,
beforeEach: function() {
application = startApp();
container = application.__container__;
sideBarService = container.lookup('service:side-bar');
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('it displays the correct title', function(assert) {
assert.expect(1);
Ember.run(function () {
sideBarService.set('title', 'Hello');
});
this.render(hbs`
{{side-bar}}
`);
var content = this.$('.side-bar-content .title').text().trim();
var serviceTitle = sideBarService.get('title');
// fails
assert.deepEqual(content, serviceTitle);
});
Interestingly, if I debug in the test and grab the component with the console and then grab the sideBarService off of the component, it is aware of the updated title value and even the value title on the component itself seems to be updated but the dom never gets updated:
//debugged in browser console
var sb = container.lookup('component:side-bar')
undefined
sb.get('title')
"Hello"
sb.get('sideBarService.title')
"Hello"
this.$('.title').text().trim()
""
Is this a run loop issue? If so what do I need to do to set it off?
edit: In regards to Toran's comment. Does this look right?
var done = assert.async();
var content = this.$('.side-bar-content .title').text().trim();
var serviceTitle = sideBarService.get('title');
setTimeout(function() {
assert.deepEqual(content, serviceTitle);
done();
});
I would probably go about fixing this by avoiding the injection in the initializer and instead using the Ember.inject.service helper.
// component
import Ember from 'ember'
const { Component, inject, computed } = Ember;
const { service } = inject;
const { alias } = computed;
export default Component.extend({
sideBarService: service('side-bar'),
title: alias('sideBarService.title')
});
Then in your test, you can pass the service when you use the component.
import Ember from 'ember';
import startApp from '../helpers/start-app';
import hbs from 'htmlbars-inline-precompile';
import { moduleForComponent, test } from 'ember-qunit';
var application, container, sideBarService;
moduleForComponent('side-bar', 'Integration | side-bar',{
integration: true,
beforeEach: function() {
application = startApp();
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('it displays the correct title', function(assert) {
assert.expect(1);
this.set('sideBarService', Ember.Object.create({
title: 'hello'
}));
this.render(hbs`
{{side-bar sideBarService=sideBarService}}
`);
var title = this.$('.side-bar-content .title').text().trim();
assert.equal(title, 'hello'); // Hopefully passes
});

Ember- integration test case on action

In ember controller
action:function(){
a:function(){
....
this.set('b',true);
}
}
I just want to write a test case for this
test('a - function test case', function(assert) {
var controller= this.subject();
controller._action().a();
assert(controller.get(b),true);
});
but this not working I'm getting undefined error.
any other way to pass this test case?
Looking to your code, I believe you're trying to use ember actions, if so you have to use actions: { ... } instead of action: function() { ... }.
And to trigger an action you use the send method.
This is an example on how to test an action in ember-cli:
app/controllers/index
import Ember from 'ember';
export default Ember.Controller.extend({
value: null,
actions: {
changeValue: function() {
this.set('value', true);
}
}
});
tests/unit/controllers/index-test.js
import {
moduleFor,
test
} from 'ember-qunit';
moduleFor('controller:index', {});
test('it exists', function(assert) {
var controller = this.subject();
assert.ok(!controller.get('value'));
controller.send('changeValue');
assert.ok(controller.get('value'));
});
This was working for me
test('it exists', function(assert) {
var controller = this.subject();
assert.ok(!controller.get('value'));
Ember.run(function(){
controller.send('changeValue');
assert.ok(controller.get('value'));
});
});

ember-qunit: You can only unload a record which is not inFlight

I have some unit tests that access the store. I would have thought this would be fine, so long as I wrapped them in a run callback. Unfortunately, that's not the case. I'm getting this error:
afterEach failed on #foo: Assertion Failed: You can only unload a record which is not inFlight.
As I understand it, this is exactly what run should be preventing. My test looks something like this:
test('#foo', function(assert) {
var store = this.store();
var model = this.subject();
Ember.run(function() {
var secondModel = store.createRecord('secondModel', { foo: 'bar' });
model.set('secondModel', secondModel);
var foo = model.get('secondModelFoo');
assert.equal(foo, 'bar');
});
});
Seems like this is no longer an issue in Ember Data v1.13.8 in combination with Ember v1.13.7.
For following setup:
models/first-model.js
import DS from 'ember-data';
export default DS.Model.extend({
secondModel: DS.belongsTo('second-model')
});
models/second-model.js
import DS from 'ember-data';
export default DS.Model.extend({
foo: DS.attr('string')
});
tests/unit/models/first-model-test.js
import Ember from 'ember';
import { moduleForModel, test } from 'ember-qunit';
moduleForModel('first-model', 'Unit | Model | first model', {
// Specify the other units that are required for this test.
needs: ['model:second-model']
});
test('it exists', function(assert) {
var model = this.subject();
// var store = this.store();
assert.ok(!!model);
});
test('#foo', function(assert) {
var store = this.store();
var model = this.subject();
Ember.run(function() {
assert.expect(1);
var secondModel = store.createRecord('second-model', { foo: 'bar' });
model.set('secondModel', secondModel);
var foo = model.get('secondModel.foo');
assert.equal(foo, 'bar');
});
});
Tests pass. Demo project repository on GitHub.

Ember App Kit and testing model hook

In Ember App Kit, there are a number of testing examples that ship with the initial repo. One of those is a basic Route Unit test. This test is trivial, if the data is hard-coded in the model hook, like this:
test("#model", function(){
deepEqual(route.model(), ['red', 'yellow', 'blue']);
});
How do you use the isolated container to test the model hook if it returns a promise from ember-data?
Here's the test:
import Activities from 'appkit/routes/activities';
var route;
module("Unit - ActivitiesRoute", {
setup: function(){
var container = isolatedContainer([
'route:activities'
]);
route = container.lookup('route:activities');
}
});
test("#model", function(){
deepEqual(route.model(), ['activity', 'activity2', 'activity3']);
});
And the actual Route's model hook:
export default Ember.Route.extend({
model: function() {
return this.get('store').find('activity');
}
});
UPDATE:
After implementing the different approaches below from kingpin2k. Here is a summary of the outcomes.
First approach: works great ... yet no promise.
Second approach: returns the promise object (appears to be resolved), but the array, and correct values are assigned to _detail property.
test("#model", function(){
deepEqual(route.model()['_detail'], ['activity', 'activity2', 'activity3']); //passes
});
I'd like for store creation to be taken care of within the module setup().
...
module("Unit - ActivitiesRoute", {
setup: function(){
var container = isolatedContainer([
'route:activities'
]);
route = container.lookup('route:activities');
var store = {
find: function(type){
return new Em.RSVP.Promise(function(resolve){
resolve(['activity', 'activity2', 'activity3']); // or made up model(s) here
});
}
};
route.set('store', store);
}
});
And the test:
test("#model", function(){
deepEqual(route.model(), ['activity', 'activity2', 'activity3']); // ???
});
Third approach:
...
module('Unit - ActivitiesRoute', {
setup: function() {
var container = isolatedContainer([
'route:activities'
]);
route = container.lookup('route:activities');
var store = {
find: function() {
var promise = new Ember.RSVP.Promise(function(resolve) {
Em.run.later(function() {
resolve(Activity.FIXTURES);
}, 10);
});
return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
promise: promise
});
}
};
route.set('store', store);
}
});
And in the test, calling route.model() returns an empty object {} :
test("#model", function(){
deepEqual(route.model(), Activity.FIXTURES); // returns {}
});
UPDATE #2
It was also necessary to add asyncTest() instead of test() and to also call start() to prevent the test runner from hanging.
asyncTest('#model', function(){
Em.run(function(){
route.model().then(function(result){
ok(result);
equal(result, Activity.FIXTURES);
start();
});
});
});
Simple approach, it's a unit test, so really you aren't testing the store, so setup a mock store and result.
route = container.lookup('route:activities');
var store = {
find: function(type){
equal(type, 'activity', 'type is activity');
return ['activity', 'activity2', 'activity3'];
}
}
route.set('store', store);
Even better you can also replicate the promise
route = container.lookup('route:activities');
var store = {
find: function(type){
equal(type, 'activity', 'type is activity');
return new Em.RSVP.Promise(function(resolve){
resolve(['activity', 'activity2', 'activity3']); // or made up model(s) here
});
}
}
route.set('store', store);
If you want to more closely replicate Ember Data you might use an ArrayProxy implementing the PromiseProxyMixin...
route = container.lookup('route:activities');
var store = {
find: function(type){
equal(type, 'activity', 'type is activity');
var promise = new Ember.RSVP.Promise(function(resolve){
Em.run.later(function(){
resolve(['activity', 'activity2', 'activity3']);
}, 10);
});
return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
promise: promise
});
}
}
route.set('store', store);
Update
Using your last approach you should implement it like this
test("#model", function(){
route.model().then(function(result){
deepEqual(result, Activity.FIXTURES); // returns {}
});
});
But, there is something tricky here, since it has an async response you'll want to wrap it in an Ember run loop
test("#model", function(){
Em.run(function(){
route.model().then(function(result){
deepEqual(result, Activity.FIXTURES); // returns {}
});
});
});

Testing Ember Data - Switching to FixtureAdapter during test runs

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/