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
});
Related
I'm writing my first question here sorry for any ambiguity.
I write an integration test for update-pw component which simple render update-pw and then fill input field with fillIn and then click save button which trigger the action savePW in update-pw.js. I only pass email(for whom we want to change password) and new password.
savePW() function further has a function call self.store.updateSingleUserPw(email, newPw) which is written in service store.js.
updateSingleUserPw(email, newPw) returns a promise after server process on API call. On basis of fulfillment or rejection of promise I show a modal.
I just want to make that promise fulfill or rejected in my test instead of server response for promise.
// integration/component/update-pw-test.js
import { module, test } from 'qunit';
import EmberObject from '#ember/object';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import Service from '#ember/service';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
const store = Service.extend({
savePW() {
self.store.updateSingleUserPw(email, newPw, function() {
console.log('this is function overriding', email, newPw);
return true;
})
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw.',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
});
test('it renders', async function(assert) {
this.application.register('service:store', store);
this.application.inject.service('store', { as: 'store' });
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = EmberObject.create({
username: 'steinauer',
email: 'lala#test.at'
});
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
// assert.equal(this.element.textContent.trim(), 'what is this');
});
});
// components/update-pw.js
import Component from '#ember/component';
export default Component.extend({
changingPassword: false,
actions: {
savePW() {
let self = this;
if (!self.get('currentUpdateAdmin.email'))
return;
let newPw = self.get('password');
let email = self.get('currentUpdateAdmin.email');
self.set('changingPassword', true);
if (!email)
return;
self.store.updateSingleUserPw(email, newPw)
.then(function() {
// Reset controller fields
self.set('password', '');
self.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, function() {
self.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(function() {
self.set('changingPassword', false);
});
}
}
});
function in Service/store.js :
updateSingleUserPw(email, newPw) {
let headers = this.get('headers');
return new Promise(function(resolve, reject) {
$.ajax({
type: 'POST',
url: ENV.api + '/accounts/updateSingleUserPw',
data: {
email: email,
pwNew: newPw
},
headers,
dataType: 'json'
}).then(function(success) {
if (success) {
resolve(newPw);
} else {
reject('password change failed');
}
}, function(xhr, status, error) {
reject(error);
});
});
}
Before trying to override function I got only rejected promise modal but after the try of overriding the function i'm getting:
Promise rejected during "it renders": Cannot read property register of undefined.
thanks for your question 🎉
Firstly can I thank you for providing your code samples, I would not have been able to solve your question had you not provided so much! I have actually simplified some of the things that you are trying to do and I think by simplifying things I have come to the solution.
Firstly I have renamed the Service that you keep using to be called password-store. Usually when an Ember developer sees a Service named store they tend to think of an ember-data store which I'm assuming you're not actually using here by the functionality that you are expecting.
I generated a very simple mock store that just had one function in it:
// app/services/password-store.js
import Service from '#ember/service';
export default Service.extend({
updateSingleUserPw(email, password) {
// TODO: do something with email & password
return Promise.resolve();
}
});
This just returns a promise so that it won't break any of the other code samples. I then updated your update-pw component to use the new password store:
// app/components/update-pw.js
import Component from '#ember/component';
import { inject as service } from '#ember/service';
function swal() {
// noop - not sure where this comes from
}
export default Component.extend({
passwordStore: service(),
changingPassword: false,
actions: {
savePW() {
if (!this.get('currentUpdateAdmin.email'))
return;
let newPw = this.get('password');
let email = this.get('currentUpdateAdmin.email');
this.set('changingPassword', true);
if (!email)
return;
this.passwordStore.updateSingleUserPw(email, newPw)
.then(() => {
// Reset controller fields
this.set('password', '');
this.set('updateModal', false);
swal({
title: 'Das hat geklappt',
type: 'success'
});
}, () => {
this.set('updateModal', false);
swal({
title: 'problems with setting new pw',
type: 'error'
});
})
.finally(() => {
this.set('changingPassword', false);
});
}
}
});
I also added a swal() function because I didn't quite know where that came from in your example. It seemed to be missing so I just ignored it.
Now lastly I have setup a template so that the test will actually pass:
// app/templates/components/update-pw.hbs
<h4>set new PW for steinauer</h4>
{{input id="password" value=password}}
<button type="button" name="button" class="save-button" {{action 'savePW'}}></button>
Now with the application fully setup here is the full example of a test that will do exactly what you were hoping to do:
// tests/integration/components/update-pw-test.js
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, fillIn, click } from '#ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import StoreService from 'your-app-name/services/password-store';
module('Integration | Component | update-pw', function(hooks) {
setupRenderingTest(hooks);
test('it renders', async function(assert) {
const passwordStore = StoreService.extend({
updateSingleUserPw(email, newPw) {
console.log('updateSingleUserPw override!!');
assert.equal(newPw, 'test123456');
return Promise.resolve();
}
});
this.owner.register('service:password-store', passwordStore);
assert.expect(2);
this.set('updateModal', true);
this.set('testing', true);
let currentUpdateAdmin = {
username: 'steinauer',
email: 'lala#test.at'
};
this.set('currentUpdateAdmin', currentUpdateAdmin);
await render(hbs`{{update-pw updateModal=updateModal currentUpdateAdmin=currentUpdateAdmin testing=testing store=store}}`);
assert.equal(this.element.querySelector('h4').textContent.trim(), 'set new PW for steinauer');
await fillIn('#password', 'test123456');
await click('.save-button');
// Template block usage:
await render(hbs`
{{#update-pw}}
template block text
{{/update-pw}}
`);
});
});
The first thing that you might notice is that we are not using this.application.register or this.application.inject. I can't remember exactly if this is how it used to be done a long time ago but this is not available for a few years in Ember.
What we end up doing is we import the StoreService from your-app-name/services/password-store (replacing your-app-name with whatever your modulePrefix is) and then we extend it while overriding the updateSingleUserPw() function. In your example it looked like you were trying to override a function called savePW() but that is actually the action name from the component and it might have been slightly confusing you.
I hope that helps, I have tested the example locally and it works perfectly well! You may also notice I added an assertion inside the service, this is quite a useful pattern to make sure that the service receives the right arguments from the component 👍
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);
}
});
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'));
});
});
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.
I'm running into a problem where the Ember application I'm testing doesn't seem to be noticing the models that I'm creating with FactoryGuy. Here's my test file:
import Ember from 'ember';
import startApp from '../helpers/start-app';
import FactoryGuy from 'factory-guy';
import { testMixin as FactoryGuyTestMixin} from 'factory-guy';
import carsFactory from "../fixtures/car";
var application, testHelper, store, make;
var TestHelper = Ember.Object.createWithMixins(FactoryGuyTestMixin);
module('Acceptance: Cars', {
setup: function() {
application = startApp();
testHelper = TestHelper.setup(application);
store = testHelper.getStore();
testHelper.make('car');
},
teardown: function() {
Ember.run(function() { testHelper.teardown(); });
Ember.run(application, 'destroy');
}
});
test('visiting /cars', function() {
equal(store.all('car').get('content.length'), 1);
visit('/cars');
andThen(function() {
equal(currentPath(), 'cars');
var li = find('li');
equal(li.length, 2);
});
});
The first and second equal assertions will succeed, but the last one will fail. Here's what my template looks like:
<ul>
{{#each car in model}}
<li>{{car.label}}</li>
{{/each}}
</ul>
And my route:
import Ember from 'ember';
export default Ember.Route.extend({
model: function () {
this.store.find('car');
}
});
What am I missing in getting the Ember app's store to get properly populated by the FactoryGuy's make method?
Edit: I also have tried adding the following line at the top of the test method and in the setup function, and it still isn't working correctly.
testHelper.handleFindMany('car', 1);
EmberDataFactoryGuy is now an ember addon, so if you are using that then the test would look like this:
import Ember from 'ember';
import startApp from '../helpers/start-app';
import { make } from 'ember-data-factory-guy';
import TestHelper from 'ember-data-factory-guy/factory-guy-test-helper';
var App;
module('Acceptance: Cars', {
setup: function() {
Ember.run(function () {
App = startApp();
TestHelper.setup();
});
},
teardown: function() {
Ember.run(function() {
TestHelper.teardown();
App.destroy();
});
}
});
test('visiting /cars', function() {
TestHelper.handleFindAll('car', 2);
visit('/cars');
andThen(function() {
equal(currentPath(), 'cars');
var li = find('li');
equal(li.length, 2);
});
});
There is a sample acceptance test just like this one in the ember-data-factory-guy repo here ( looks pretty much just like this one though ):
https://github.com/danielspaniel/ember-data-factory-guy/blob/master/tests/acceptance/users-view-test.js
Anyway, there is no more hassle of setting the store, or creating TestHelper, it's all done for you, and setup automatically when you start the application.