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'))
Related
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()
});
...
});
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 have not been able to test React Router with context successfully. I am using
react 0.13.3
react-router 0.13.3
jest 0.3.0
node 0.10.33
and have tried these approaches:
https://labs.chie.do/jest-testing-with-react-router/
https://gist.github.com/alanrubin/da3f740308eb26b20e70
Is there a definitive example?
All links to the "super-secret guide" mentioned in this question (which does not use Jest) are now broken. When I was able to view that guide, it didn't provide any more information than the first link listed above.
Not sure if this is what you're looking for exactly, but I got around this by making a helper function that I use when writing jest tests for components that depend on router state.
//router-test-helper
var Router = require('react-router'),
Route = Router.Route,
TestLocation = require('react-router/lib/locations/TestLocation');
module.exports = function(React){
TestUtils = React.addons.TestUtils;
return {
getRouterComponent: function(targetComponent, mockProps) {
var component,
div = document.createElement('div'),
routes = [
React.createFactory(Route)({
name: '/',
handler: targetComponent
})
];
location = new TestLocation('/');
Router.run(routes, location, function (Handler) {
var mainComponent = React.render(React.createFactory(Handler)(mockProps), div);
component = TestUtils.findRenderedComponentWithType(mainComponent, targetComponent);
});
return component;
}
};
};
I didn't write all of this on my own, most of it I think I pulled from that now defunct guide you linked to. If I remember right... it's been a while.
After you have that you can use this in your tests kinda like this.
//test-example
jest.dontMock('../src/js/someComponent');
var React = require('react/addons');
var TestUtils = React.addons.TestUtils;
var routerHelper = require('../router-test-helper')(React);
var SomeComponent = require('../srcs/js/someComponent');
describe('Some Component', function(){
it('should be testable', function(){
var mockProps = {};
var renderedComponent = routerHelper.getRouterComponent(SomeComponent, mockProps);
// Test your component as usual from here.....
///////////////////////////////////////////////
var inputs = TestUtils.scryRenderedDOMComponentsWithTag(renderedComponent, 'input');
//blah blah blah
});
});
This assumes you have React and the helper in your unmocked module paths
If you're actually trying to test things specific to certain routes, or transitioning between routes... I'm not sure if this is a good approach. May be better to use something more intergration test-y, like selenium or something.. Also... this probably won't work once the 1.0 of react router comes out. But it may be even easier to test things thing 'The React Way'(tm) because all the routing stuff will be handled through props. At least that's the impression I get from the the little bit I've read into it.
To anyone who happens to stuck with this problem.
Here's the setup that I've ended up with for my context dependent components (stripped down for simplicity, of course):
// dontmock.config.js contains jest.dontMock('components/Breadcrumbs')
// to avoid issue with hoisting of import operators, which causes
// jest.dontMock() to be ignored
import dontmock from 'dontmock.config.js';
import React from "react";
import { Router, createMemoryHistory } from "react-router";
import TestUtils from "react-addons-test-utils";
import Breadcrumbs from "components/Breadcrumbs";
// Create history object to operate with in non-browser environment
const history = createMemoryHistory("/products/product/12");
// Setup routes configuration.
// JSX would also work, but this way it's more convenient to specify custom
// route properties (excludes, localized labels, etc..).
const routes = [{
path: "/",
component: React.createClass({
render() { return <div>{this.props.children}</div>; }
}),
childRoutes: [{
path: "products",
component: React.createClass({
render() { return <div>{this.props.children}</div>; }
}),
childRoutes: [{
path: "product/:id",
component: React.createClass({
// Render your component with contextual route props or anything else you need
// If you need to test different combinations of properties, then setup a separate route configuration.
render() { return <Breadcrumbs routes={this.props.routes} />; }
}),
childRoutes: []
}]
}]
}];
describe("Breadcrumbs component test suite:", () => {
beforeEach(function() {
// Render the entire route configuration with Breadcrumbs available on a specified route
this.component = TestUtils.renderIntoDocument(<Router routes={routes} history={history} />);
this.componentNode = ReactDOM.findDOMNode(this.component);
this.breadcrumbNode = ReactDOM.findDOMNode(this.component).querySelector(".breadcrumbs");
});
it("should be defined", function() {
expect(this.breadcrumbNode).toBeDefined();
});
/**
* Now test whatever you need to
*/
I'm trying to test a simple component that uses action delegation. I want to test that a certain action is sent to the actionDelegate of a component. I was thinking of using Sinon to check that the action was being sent, but I can't work out how to replicate the structure that Ember uses to send its actions to its delegates/targets.
What structure would my sinon "spy delegate" object take in order for me to check that the component is delegating the event using "send" when the user clicks on a button?
I've created an example of the kind of thing I want to test at http://jsfiddle.net/L3M4T/ but it haven't a testing harness around it (it's kind of a big job to put a testing harness around a component just for a simple js fiddle - in fact it was quite a bit of a job to get this component into the shape I wanted to explain this problem).
Here's my component:
App.AppProfileComponent = Ember.Component.extend({
actionDelegate: null,
actions: {
hello: function(person) {
if(!Ember.isEmpty(this.get('actionDelegate'))) {
this.get('actionDelegate').send('hello', person);
}
}
}
});
And my inital try that didn't work was simply to write a test that had this fragment in it (using sinon & qunit):
visit("/").click("button").then(function() {
equal(actionDelegateSpy.called, true, "Action delegate should be called when button pressed");
});
I think it's pretty obvious why that didn't work, but since I've tried the following, which also didn't work:
var actionDelegateSpy = sinon.spy();
var actionDelegate = Ember.ObjectController.extend({
actions: {
"hello" : actionDelegateSpy
}
}).create();
then testing by passing in the actionDelegate defined above as the actionDelegate on the component for a test.
I fixed my own issue... Silly me:
test("delegates its hello action to actionDelegate", function() {
var actionDelegateSpy;
Ember.run(function() {
actionDelegateSpy = sinon.spy();
var actionDelegate = Ember.ObjectController.extend({
actions: {
"hello" : actionDelegateSpy
}
}).create();
controller.set('actionDelegate', actionDelegate);
});
visit("/").click("button")
.then(function() {
equal(actionDelegateSpy.called, true, "Action delegate should be called when hello button pressed");
});
});
I am unit testing my controller using mocha. My controller looks like:
AS.MyController = Ember.ObjectController.extend(Ember.Validations.Mixin, {
name: null,
description: null,
init: function () {
this._super();
this.get('store').find('something');
},
....
});
And my test begins like:
describe("MyControllerTest", function () {
//tried but didn't work
//delete AS.MyController.init;
var controller = AS.MyController.create();
.....
})
and the browser always throws error on "this.get('store')" call in init. I am not sure if I need to stub things out or there is a work around for it because my test case doesn't rely on store at all. In either case, I couldn't find much out there and would really appreciate any feedback.
Thanks, Dee
JSBIN : http://jsbin.com/aMASeq/3/
UPDATE :
There can be many ways to tackle this issue, but what I ended up doing is re-structuring the controller code a bit by putting all the function calls to store into separate actions and then in init I make calls to these action functions using this.send('actioName'). In my unit test, before instantiating the controller, I reopen the controller to modify these action functions(its easier to change action function than to change init function itself, when trying to change init I always got into some js error). Eg:
AS.MyController.reopen({actions: {setSomeActionThatUsesStore: function () {
//do something that doesn't involve using store
}}});
Controllers get access to the store from the container. You can create a mock container and instantiate the controller with it.
var mockContainer = new Ember.Container();
mockContainer.register('store:main', Ember.Object.extend({
find: function() { ... }
});
var controller = App.PostController.create({ container: mockContainer });
If you need access to the real store then you can just grab the controller from your App's container.
var controller = App.__container__.lookup('controller:post');
That will instantiate a PostController for you that has all of it's dependencies (such as store) wired together.