I have a component that gets menu items and renders navbar. So now I'm writing integration test, and I want to make sure, that component renders right links and labels. First of all, I added router initialization to make link-to display href prop:
moduleForComponent('main-menu', 'Integration | Component | main menu',
{
integration: true,
setup() {
const router = getOwner(this).lookup('router:main');
router.setupRouter();
}
});
Now I want to create some fake routes to test component, and to be independent from application router's setup. So I try to use map function:
moduleForComponent('main-menu', 'Integration | Component | main menu', {
integration: true,
setup() {
const router = getOwner(this).lookup('router:main');
router.map(function() {
this.route('link1');
this.route('link2');
});
router.setupRouter();
}
});
And I get Promise rejected before "it renders": router.map is not a function. So how should I implement "fake routes" for tests?
Ok, solved the problem. If someone will ever need something similar, here's how I did it:
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import { getOwner } from '#ember/application';
import CustomRouter from 'app/lib/router';
moduleForComponent('main-menu', 'Integration | Component | main menu', {
integration: true,
setup() {
const application = getOwner(this),
Router = CustomRouter.extend()
;
Router.map(function() {
this.route('link1');
this.route('link2');
});
application.register('router:main', Router.extend());
application.lookup('router:main').setupRouter();
}
});
test('some awesome tests', function(assert) {
const menuItems = [
{url: 'link1', label: 'link1', href: '/link1'},
{url: 'link2', label: 'link2', href: '/link2'},
]
;
this.set('items', menuItems);
this.render(hbs`{{main-menu items=items}}`);
// some cool tests that now can check href attributes of links
// and don't depend on app's router setup
});
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 👍
I've been looking for a good pattern to unit test routes I have configured in my application so that I know the specified modules exist on disk as defined.
Here is an example route configuration:
import { Aurelia, PLATFORM } from 'aurelia-framework';
import { Router, RouterConfiguration } from 'aurelia-router';
export class App {
params = new bindParameters();
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
config.title = 'Aurelia';
config.map([{
route: ['', 'home'],
name: 'home',
settings: { icon: 'home' },
moduleId: PLATFORM.moduleName('../home/home'),
nav: true,
title: 'Home'
}, {
route: 'sample',
name: 'sample',
settings: { icon: 'education' },
moduleId: PLATFORM.moduleName('../sample/index'),
nav: true,
title: 'Sample Information'
}]);
this.router = router;
}
}
class bindParameters {
user = "user_name";
}
To test it I took the approach of passing in an instance of a router then checking to see if it exists:
import { App } from './app';
import jasmine from 'jasmine';
import { Container } from "aurelia-framework";
import { RouterConfiguration, Router } from "aurelia-router";
describe('application routes', function () {
let app: App;
let router: Router;
let routerConfiguration: RouterConfiguration;
let configureRouter: Promise<void>;
beforeEach(() => {
var container = new Container().makeGlobal();
routerConfiguration = container.get(RouterConfiguration);
router = container.get(Router);
app = new App();
app.configureRouter(routerConfiguration, router);
configureRouter = router.configure(routerConfiguration);
routerConfiguration.exportToRouter(router);
});
it('should exist for sample', function () {
expect(router).not.toBeNull();
//configureRouter.then(function () {
//var route = router.routes.find((route) => route.name == 'sample');
// add some assert that the sample module can be found
// done();
//});
});
});
My current problem is that the container is returning a null Router as shown by the current test. The closest pattern I have found to what I am trying to do is in this question.
What am I missing in my example test and also is there a better way to test route configuration?
It seems #thinkOfaNumber was right. Turns out my test was good, but I was missing reflect-metadata. When I applied the fix outlined in this stackoverflow post my tests passed.
I am getting the following error when running an integration component test. Any ideas why? The only slightly weird thing is that the {{input-mask}} component is used from an addon.
TypeError: (intermediate value).on is not a function
at http://localhost:7357/assets/vendor.js:182304:7
at mod.state (http://localhost:7357/assets/vendor.js:152:29)
at tryFinally (http://localhost:7357/assets/vendor.js:32:14)
at requireModule (http://localhost:7357/assets/vendor.js:150:5)
at requireFrom (http://localhost:7357/assets/vendor.js:123:12)
at reify (http://localhost:7357/assets/vendor.js:108:22)
at mod.state (http://localhost:7357/assets/vendor.js:151:17)
at tryFinally (http://localhost:7357/assets/vendor.js:32:14)
at requireModule (http://localhost:7357/assets/vendor.js:150:5)
at Ember.DefaultResolver.extend._extractDefaultExport (http://localhost:7357/assets/vendor.js:66617:20)
Test:
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
moduleForComponent('phone-mask', 'Integration | Component | phone mask', {
integration: true
});
test('it can format landlines', function(assert) {
assert.expect(1);
this.set('value', 1111111111);
this.render(hbs`{{phone-mask value=value}}`);
assert.equal(this.$('input').val(), '(11) 1111 1111');
});
Component:
import Ember from 'ember';
import layout from './template';
import { startsWith } from '../../utils/elm-helpers';
const { Component, observer } = Ember;
export default Component.extend({
layout,
// public
value: null,
format: '(99) 9999 9999',
iconName: 'phone',
disabled: false,
valueUpdated: observer('value', function() {
if (startsWith(this.get('value'), '04')) {
this.set('format', '9999 999 999');
this.set('iconName', 'mobile');
} else {
this.set('format', '(99) 9999 9999');
this.set('iconName', 'phone');
}
})
});
Template:
<div class="input-group">
<span class="input-group-addon">
<i class="fa fa-{{iconName}}"></i>
</span>
{{input-mask mask=format name=name class="form-control" unmaskedValue=value disabled=disabled}}
</div>
I don't know why the test is failing, but here are some suggestions on refactoring the component.
import Ember from 'ember';
import layout from './template';
const {
Component,
computed,
get
} = Ember;
export default Component.extend({
layout,
// public
disabled: false,
value: null,
// I'm guessing at what the `startsWith` helper does. Even if
// `computed.match` doesn't do the correct thing, I'd keep the
// `valueStartsWithFour` computed property and wrap whatever
// logic you need in it.
valueStartsWithFour: computed.match('value', /^04/),
format: computed('valueStartsWithFour', function() {
const valueStartsWithFour = get(this, 'valueStartsWithFour');
return valueStartsWithFour ? '9999 999 999' : '(99) 9999 9999';
}),
iconName: computed('valueStartsWithFour', function() {
const valueStartsWithFour = get(this, 'valueStartsWithFour');
return valueStartsWithFour ? 'mobile' : 'phone';
})
});
As a general rule of thumb, it's preferable to use computed properties when you can. From the guides:
Observers are used heavily within the Ember framework itself, but for
most problems Ember app developers face, computed properties are the
appropriate solution.
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
});
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.