I have read and followed EmberJS Service Injection for Unit Tests (Ember QUnit) but I'm still not able to figure where the problem is.
I would like to test if my authentication is working as expected. I have written authenticator for ember-simple-auth and session is injected into route. Code itself is working without any issues.
export default Ember.Route.extend({
authManager: Ember.inject.service('session'),
...
(in actions):
this.get('authManager').invalidate()
Now, I want to create a test which will test if my authentication is working as I expect. So I wish to use authManager directly.
moduleFor('route:index', 'Unit | Route | xyz', {
needs: ['service:session']
});
test('2', function(assert) {
let route = this.subject();
let s = route.get('authManager');
When I print the content of 's', I get ''. If I change this to something else, then response is undefined as can be expected. Problem is when I want to obtain property 'isAuthenticated' or run 'invalidate()'. In these cases I got 'undefined'. What am I doing wrong?
As of Ember 2.13, the correct solution to this is to use this.register:
test('my test', function(assert) {
this.register('service:session', Ember.Service.extend({
/* mock code */
}));
let subject = this.subject();
// test code goes here...
}
In a unit test, we prefer to use mock objects instead of services. In integration tests, we may use real services instead of mocks.
To mock a service, in a unit test:
var stubMyService = Ember.Object.extend({
//This is a mock object, write a code to test component/route!
invalidate: function() {
return 'invalidateCalled';
},
isAuthenticated: function(){
return true;
}
});
To inject this mock object to your component/route use this.subject() creation as following:
test('2', function(assert){
var component = this.subject({
authManager: stubMyService.create()
});
...
});
Related
How can I test this code in Ember? Explain me please the concept, in general.
// app/routes/products/new.js
import Ember from 'ember';
export default Ember.Route.extend({
model() {
return this.store.createRecord('product');
},
actions: {
willTransition() {
this._super(...arguments);
this.get('controller.model').rollbackAttributes();
}
}
});
I have no idea how to make this. May be stub model in route? I found that store is not available in route test.
After Ruby and RSpec, all these new javascript world is confusing a little bit) But I'd like to learn it anyway.
In unit tests the idea is to stub all external dependencies. In ember you can do this:
// tests/unit/products/new/route-test.js
test('it should rollback changes on transition', function(assert) {
assert.expect(1);
let route = this.subject({
controller: Ember.Object.create({
model: Ember.Object.create({
rollbackAttributes() {
assert.ok(true, 'should call rollbackAttributes on a model');
}
})
})
});
route.actions.willTransition.call(route);
});
Basically you stub controller and model passing them to this.subject(), then call whatever function you are testing (in this case you have to use call or apply to call an action with the correct scope), and then assert that rollbackAttributes() was called.
assert.expect(1); at the start of a test tells QUnit to wait for exactly 1 assertion.
I try to write my first unit test for a route.
project/files
actions: {
afterSave(savedFile){
// ... some code
let controller = this.controllerFor('project.files');
// ...
}
}
the test:
test('save file', function(assert) {
let route = this.subject();
console.log(route);
let project;
Ember.run(() => {
project = route.get('store').createRecord('project', {
id: '1',
name: 'test'
});
let afterSave = route.get('actions.afterSave');
afterSave(project);
});
assert.ok(true);
})
The problem that I am getting TypeError: Cannot read property 'controllerFor' of undefined.
It looks like this is undefined.
If you have a look at Testing Routes section from Ember Guides, you can see its suggestion is to separate the action and the function.
I can suggest it.
It uses send method of routes, such as: route.send('afterSave');
But if you only want to make run your code, call afterSave action from your test code such as: afterSave.bind(route)(project);. Ref: bind function (I don't suggest this. Also I don't suggest you to retrieve action such as: route.get('actions.afterSave'))
How can I can I test my components that get an ember-data model passed into it as props?
For example:
{{#queue/review/moderatable-text model=activity property="info_name" handleModeration="handleModeration"}}
{{pro-form-textfield value=activity.info_name}}
{{/queue/review/moderatable-text}}
where activity is a model instance.
How do I setup my integration test to pass in activity and to test it where component can save the model?
I tried to stub out as if it's pure ember objects:
test('it sets approved', function(assert) {
// Set any properties with this.set('myProperty', 'value');
// Handle any actions with this.on('myAction', function(val) { ... });" + EOL + EOL +
this.set('property', 'info_title');
this.set('model', Ember.Object.create({counterpart: Ember.Object.create()}))
// Template block usage:" + EOL +
this.render(hbs`
{{#queue/review/moderatable-text property=property}}
{{pro-form-textfield value=value}}
{{/queue/review/moderatable-text}}
`);
this.$('.approve-button').click();
assert.ok(this.get('approved'));
});
but then I'd have to create my own save() methods and others.
Thoughts?
but then I'd have to create my own save() methods and others.
It's good, you can go with that approach. In my opinion, it's advantage that you can create your own methods. You can place assertions in them. So, for example, if you expect component to call save method of model you can place assertion in save method body:
Ember.Object.create({
counterpart: Ember.Object.create(),
save() {
assert.ok('save method called');
}
});
This gives you better control over testing behavior in tests.
Use this.inject.service('store'); in your the beforeEach() of the moduleFor[...]() object:
beforeEach: function() {
this.inject.service('store');
this.user = this.store.createRecord('user', { name: "Frank" });
}
Then you can use this.user in your tests. Also explained here:
https://guides.emberjs.com/release/tutorial/service/
I'm installing the 'ember-moment' helper and then I'm using it in components. As soon as I do that, those components' tests break saying pretty much nothing:
not ok 120 PhantomJS 1.9 - StatusCardComponent: it renders
---
actual: >
null
message: >
Died on test #2 at http://localhost:7357/assets/test-support.js:2779
at test (http://localhost:7357/assets/test-support.js:1796)
at http://localhost:7357/assets/client.js:11190
at http://localhost:7357/assets/vendor.js:150
at tryFinally (http://localhost:7357/assets/vendor.js:30)
at http://localhost:7357/assets/vendor.js:156
at http://localhost:7357/assets/test-loader.js:29
at http://localhost:7357/assets/test-loader.js:21
at http://localhost:7357/assets/test-loader.js:40
at http://localhost:7357/assets/test-support.js:5545: Assertion Failed: A helper named 'moment' could not be found
Log: >
...
And here is the code of the test itself, it's just the auto generation:
import {
moduleForComponent,
test
} from 'ember-qunit';
moduleForComponent('event-card', {
// Specify the other units that are required for this test
// needs: ['component:foo', 'helper:bar']
});
test('it renders', function(assert) {
assert.expect(2);
// Creates the component instance
var component = this.subject();
assert.equal(component._state, 'preRender');
// Renders the component to the page
this.render();
assert.equal(component._state, 'inDOM');
});
I tried a bunch of ways of adding this helper to the test as a dependency like needs: ['helper:moment'] and adding its initializer signature to the moduleForComponent function's params, but nothing worked and I can't find any information about it. Help is much appreciated.
Edit: According to this PR, you should be able to specify { integration: true } to turn your existing unit test into an integration test, which eliminates the need for needs:.
moduleForComponent('post', { integration: true });
More background can be found here: https://github.com/rwjblue/ember-qunit/issues/108. Essentially, the integration flag disables the isolated container for the test, so that it has access to the app's resolver.
Note: Requires ember-qunit#0.2.11.
Old Answer: Take a look at this PR: https://github.com/switchfly/ember-test-helpers/pull/21 which adds moduleForIntegration. Here's the relavant bit:
This adds a new kind of test module that fills a gap in the current
set of possible tests. So far we have:
• unit tests that are good for testing algorithmic correctness in isolation.
• acceptance tests that are good for testing within a complete
application.
But we're missing the ability to integration test a unit smaller than
whole-application. This PR adds a new TestModuleForIntegration that
lets you drive an integration test with a template. I think this is
often a more appropriate way to test Ember Components than a unit
test, because interesting components tend to have rich dependence on
other components.
You can see it in use in liquid-fire tests:
/* global sinon */
import Ember from "ember";
import moduleForIntegration from "../../helpers/module-for-integration";
import { test } from "ember-qunit";
moduleForIntegration('Integration: liquid-if');
test('it should render', function(assert) {
this.set('person', 'Tom');
this.render(`
{{#liquid-if isReady}}
{{person}} is ready
{{else}}
{{person}} is not ready
{{/liquid-if}}
`); // }}`)
assert.equal(this.$().text().trim(), 'Tom is not ready');
this.set('person', 'Yehuda');
assert.equal(this.$().text().trim(), 'Yehuda is not ready');
this.set('isReady', true);
assert.equal(this.$().text().trim(), 'Yehuda is ready');
});
So this is what ended up working:
import {
moduleForComponent,
test
} from 'ember-qunit';
import Ember from 'ember';
import { initialize } from '../../../../../initializers/ember-moment';
moduleForComponent('event-card', {
// Specify the other units that are required for this test
// needs: ['helper:moment'],
setup: function (container) {
Ember.run(function () {
// these two arguments are not used
// but probably still good to pass them in
// in the event we leverage them in the future
initialize(container);
});
}
});
test('it renders', function (assert) {
assert.expect(2);
// Creates the component instance
var component = this.subject();
assert.equal(component._state, 'preRender');
// Renders the component to the page
this.render();
assert.equal(component._state, 'inDOM');
});
Do you see that line that is commented out that reads:
// needs: ['component:foo', 'helper:bar']
You need to uncomment it out and make it read:
needs: ['helper:moment']
That should help, if your moment helper is registered correctly
Quick summary/tldr:
It seems that Ember's container lookup process + Ember-CLI's module resolver doesn't allow manually un-registering a service and then registering a replacement if the original service can be resolved using the resolver (I want to do the method described here, but it doesn't work)
How can I mock an Ember-CLI service in an acceptance test without using a hacky, custom resolver? (example project/acceptance test here)
Detailed explanation + example
Create a new service that is injected into a controller:
ember generate service logger
services/logger.js
export default Ember.Object.extend({
log: function(message){
console.log(message);
}
});
initializers/logger-service.js
export function initialize(container, application) {
application.inject('route', 'loggerService', 'service:logger');
application.inject('controller', 'loggerService', 'service:logger');
}
The service is accessed through its injected name, loggerService, in an action handler on the application controller:
Use the service in a controller
templates/application.hbs
<button id='do-something-button' {{action 'doSomething'}}>Do Something</button>
controllers/application.hs
export default Ember.Controller.extend({
actions: {
doSomething: function(){
// access the injected service
this.loggerService.log('log something');
}
}
});
Attempt to test that this behavior occurs correctly
I created an acceptance test that checks that the button click triggered the service. The intent is to mock out the service and determine if it was called without actually triggering the service's implementation -- this avoids the side-effects of the real service.
ember generate acceptance-test application
tests/acceptance/application-test.js
import Ember from 'ember';
import startApp from '../helpers/start-app';
var application;
var mockLoggerLogCalled;
module('Acceptance: Application', {
setup: function() {
application = startApp();
mockLoggerLogCalled = 0;
var mockLogger = Ember.Object.create({
log: function(m){
mockLoggerLogCalled = mockLoggerLogCalled + 1;
}
});
application.__container__.unregister('service:logger');
application.register('service:logger', mockLogger, {instantiate: false});
},
teardown: function() {
Ember.run(application, 'destroy');
}
});
test('application', function() {
visit('/');
click('#do-something-button');
andThen(function() {
equal(mockLoggerLogCalled, 1, 'log called once');
});
});
This is based on the talk Testing Ember Apps: Managing Dependency by mixonic that recommends unregistering the existing service, then re-registering a mocked version:
application.__container__.unregister('service:logger');
application.register('service:logger', mockLogger, {instantiate: false});
Unfortunately, this does not work with Ember-CLI. The culprit is this line in Ember's container:
function resolve(container, normalizedName) {
// ...
var resolved = container.resolver(normalizedName) || container.registry[normalizedName];
// ...
}
which is part of the container's lookup chain. The issue is that the container's resolve method checks the resolver before checking its internal registry. The application.register command registers our mocked service with the container's registry, but when resolve is called the container checks with the resolver before it queries the registry. Ember-CLI uses a custom resolver to match lookups to modules, which means that it will always resolve the original module and not use the newly registered mock service. The workaround for this looks horrible and involves modifying the resolver to never find the original service's module, which allows the container to use the manually registered mock service.
Modify Resolver to avoid resolving to original service
Using a custom resolver in the test allows the service to be successfully mocked. This works by allowing the resolver to perform normal lookups, but when our service's name is looked up the modified resolver acts like it has no module matching that name. This causes the resolve method to find the manually registered mock service in the container.
var MockResolver = Resolver.extend({
resolveOther: function(parsedName) {
if (parsedName.fullName === "service:logger") {
return undefined;
} else {
return this._super(parsedName);
}
}
});
application = startApp({
Resolver: MockResolver
});
This seems like it shouldn't be necessary and doesn't match the suggested service mocking from the above slides. Is there a better way to mock this service?
The ember-cli project used in this question be found in this example project on github.
Short version of the solution: your registered mock service must have a different service:name than the "real" service you're trying to mock.
Acceptance test:
import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from 'container-doubling/tests/helpers/start-app';
var application;
let speakerMock = Ember.Service.extend({
speak: function() {
console.log("Acceptance Mock!");
}
});
module('Acceptance | acceptance demo', {
beforeEach: function() {
application = startApp();
// the key here is that the registered service:name IS NOT the same as the real service you're trying to mock
// if you inject it as the same service:name, then the real one will take precedence and be loaded
application.register('service:mockSpeaker', speakerMock);
// this should look like your non-test injection, but with the service:name being that of the mock.
// this will make speakerService use your mock
application.inject('component', 'speakerService', 'service:mockSpeaker');
},
afterEach: function() {
Ember.run(application, 'destroy');
}
});
test('visit a route that will trigger usage of the mock service' , function(assert) {
visit('/');
andThen(function() {
assert.equal(currentURL(), '/');
});
});
Integration test (this is what I was originally working on that caused me issues)
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import Ember from 'ember';
let speakerMock = Ember.Service.extend({
speak: function() {
console.log("Mock one!");
}
});
moduleForComponent('component-one', 'Integration | Component | component one', {
integration: true,
beforeEach: function() {
// ember 1.13
this.container.register('service:mockspeaker', speakerMock);
this.container.injection('component', 'speakerService', 'service:mockspeaker');
// ember 2.1
//this.container.registry.register('service:mockspeaker', speakerMock);
//this.container.registry.injection('component', 'speakerService', 'service:mockspeaker');
}
});
test('it renders', function(assert) {
assert.expect(1);
this.render(hbs`{{component-one}}`);
assert.ok(true);
});
You can register your mock and inject it instead of the original service.
application.register('service:mockLogger', mockLogger, {
instantiate: false
});
application.inject('route', 'loggerService', 'service:mockLogger');
application.inject('controller', 'loggerService', 'service:mockLogger');
I use this approach for mocking the torii library in my third-party login acceptance tests. I hope there will be a nicer solution in the future.
The existing answers work well, but there's a way that avoids renaming the service and skips the inject.
See https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/helpers/module-for-acceptance.js#L14 and usage at https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/acceptance/keyboard-shortcuts-test.js#L13
I'll present it here as an update to the test helper I previously had here, so it's a drop-in replacement, but you may just want to follow the links above instead.
// tests/helpers/override-service.js
// Override a service with a mock/stub service.
// Based on https://github.com/ember-weekend/ember-weekend/blob/fb4a02353fbb033daefd258bbc032daf070d17bf/tests/helpers/module-for-acceptance.js#L14
// e.g. used at https://github.com/ember-weekend/ember-weekend/blob/fb4a02/tests/acceptance/keyboard-shortcuts-test.js#L13
//
// Parameters:
// - newService is the mock object / service stub that will be injected
// - serviceName is the object property being replaced,
// e.g. if you set 'redirector' on a controller you would access it with
// this.get('redirector')
function(app, newService, serviceName) {
const instance = app.__deprecatedInstance__;
const registry = instance.register ? instance : instance.registry;
return registry.register(`service:${serviceName}`, newService);
}
Plus performing the jslint and helper registration steps from https://guides.emberjs.com/v2.5.0/testing/acceptance/#toc_custom-test-helpers
I can then call it like this, in my example stubbing out a redirect (window.location) service, which we want to do because redirecting breaks Testem:
test("testing a redirect's path", function(assert) {
const assertRedirectPerformed = assert.async();
const redirectorMock = Ember.Service.extend({
redirectTo(href) {
assert.equal(href, '/neverwhere');
assertRedirectPerformed();
},
});
overrideService(redirectorMock, 'redirector');
visit('/foo');
click('#bar');
});