Ember RC7: Testing Routes - ember.js

I would like to test the route setup in my app.
I'm using Qunit and the ember-testing helpers.
I have a test like the following:
test("visit can handle wrong urls", function() {
var urlToVisit = "/foo/bogus";
visit(urlToVisit).then(function () {
// this will show me /foo/bogus even though the route does not exist
console.log(app.__container__.lookup('router:main').location.path);
});
});
The problem is, I can't distinguish between a failed and a successful visit.
Any ideas?

I'm not a QUnit expert but I guess one approach you could take is to test for the currentPath instead of the URL, you can get it from the ApplicationController, something like this:
This test should pass
test("visit 'index' and assert it exists", function() {
var urlToVisit = "/";
visit(urlToVisit).then(function () {
equal(App.__container__.lookup('controller:application').get('currentPath'), 'index', 'At route index');
});
});
But this should fail
test("visit wrong URL and fail", function() {
var urlToVisit = "/posts";
visit(urlToVisit).then(function () {
equal(App.__container__.lookup('controller:application').get('currentPath'), 'posts', 'Fail at non existent route posts');
});
});
Simple demo here.
Hope it helps.

Related

Injection of service into Ember tests

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()
});
...
});

Redirect in Ember Router

I have a situation where we used to have a login page but now we use a separate oAuth page. I'm trying to clean out a bunch of code, but I need to worry about people who have bookmarked the login route. Ideally, I'd like to be able to do something like this:
Router.map(function () {
this.route('login', {redirectTo: 'index'});
})
and then be able to get rid of the logic in my loginRoute:
var LoginRoute = Ember.Route.extend({
beforeModel: function (transition) {
var result = this._super(transition);
if (transition.isActive === false) {
// Route canceled for auth
return result;
} else {
return this.transitionTo('index');
}
}
});
Is that possible or do I absolutely have to keep my login route?
You can use ember-redirect addon which lets you do what you want:
let Router = Ember.Router.extend({
location: config.locationType,
redirects: {
login: 'index'
}
});

How can I test React Router with Jest?

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
*/

Emberjs call a method from an other object

This might be a silly question, but I can't find out anything about it anywhere...
I create a method in one of my controller to verify if the user session is still good, and I'm using this method in almost every page of my app in my beforeModel. But the thing is that I don't want to copy/paste the code every time in every route, this will be dirty and I really don't like it.
Lets say I have this controller :
App.LoginController = Ember.ObjectController.extend({
...
isSession: function() {
var session = this;
Ember.$
.get(host + '/session', function(data) {
console.log('DEBUG: Session OK');
})
.fail(function() {
console.log('DEBUG: Session FAIL');
session.transitionToRoute('login');
});
}
});
How can I call it in this router :
App.HomeRoute = Ember.Route.extend({
beforeModel: function(transition) {
//Here
},
model: function() {
return this.store.all('login');
}
});
I've tried this this.get('loginController').isSession(); but I receive this error Error while loading route: TypeError: Cannot call method 'isSession' of undefined
Thanks for the help !
[edit]
I don't have much to show but this :
My map
App.Router.map(function() {
this.route('login', { path: '/' });
this.route('home');
this.resource('enquiries', function() {
this.route('enquiry', { path: '/:enquiry_id' }, function() {
this.route('update');
});
});
});
Most likely I only Have a LoginController and my HomeRoute. (its the beginning of the app)
I don't need to create a Route for my Login because I have an action helper in my login template and I'm redirected to my Home template after that.
You need to use controllerFor() method in order to call method on controller from router. If method is an action you need to use send() method, like this.controllerFor('login').send('isSession')
App.HomeRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
transition.abort();
this.controllerFor('login').isSession()
}
});
If you don't need a return value from isSession you might consider making it an action on a top-level route. The router.send method in the docs has a pretty good example of how you declare actions as well as how you call them. Note that send is also a method you can call on a controller. Actions bubble up from a controller, to the parent route, and then all the way up the route hierarchy, as shown here

View Controller not getting called

I have set up a view in ember and rendered it on the page like this
App.TestView = Ember.View.extend({
template: Ember.Handlebars.compile('<h1>Heading</h1>')
});
{{view App.TestView}}
But if I create the controller nothing happens
App.TestController = Ember.Controller.extend({
init: function() {
console.log('CONTROLLER HERE');
this._super();
}
});
Any ideas why this happens?
When you create a view manually (like you are doing) it doesn't use the test controller. If you hit a test route it will use the associated test controller and test view.
In your case based on your comments below you probably want to set up some routes and have them use the associated controllers and views.
Check out this: http://emberjs.com/guides/routing/defining-your-routes/
Maybe something like this
App.Router.map(function() {
this.resource('gallery', { path: '/gallery/:gallery_id' }, function() {
this.resource('photo', { path: 'photo/:photo_id' });
});
});
You are missing a route for your example to work: http://jsbin.com/IGIvuhe/2/edit
Add this and it will work:
App.Router.map(function(){
this.route("test", {path: '/'});
});
Hope it helps.