How can I test React Router with Jest? - unit-testing

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

Related

Ember JS: How to import Material Components Web JS

I'm trying to use Material Components Web(MDC-Web) with an EmberJS application. I've installed material-components-web with Yarn.
yarn add material-components-web --dev
This installed material-components-web#0.41.0.
I have the sass/CSS working but can't figure out how to include/import the JS. Could someone show me how it would import that into my component js file and/or my component template file.
I get an no file error when I do an
app.import('node_modules/material-components-web/dist/material-components-web.js')
Thanks for any help!
This is how I normally use Material Components Web in my Ember project. Has worked for me since Ember version 2.x upto the latest 3.x.
First in ember-cli-build.js. Import the required js file
app.import('node_modules/material-components-web/dist/material-components-web.js', {
using: [{
transformation: 'fastbootShim'
}]
});
The using part is only applicable if you use Fastboot (which you should) so as to exclude the file from executing in a Fastboot environment.
Then fear not, in an Ember component on the didInsertElement hook, activate the MDC component for example for a modal drawer component I have used code like this.
tagName: 'aside',
classNames: ['mdc-drawer', 'mdc-drawer--modal', 'app-drawer'],
didInsertElement: function () {
let component = this;
let componentJqueryObject = component.$();
let componentElement = componentJqueryObject[0];
let MDCDrawer = mdc.drawer.MDCDrawer;
let drawer = new MDCDrawer(componentElement);
$('header').on('click', '.drawer-menu', function() {
drawer.open = !drawer.open;
});
$('body').on('click', 'main', function() {
drawer.open = false;
});
$('aside').on('click', 'a', function() {
drawer.open = false;
});
..........
For a MDC top-app-bar component (latest MDC only) I've used this
tagName: 'header',
classNames: ['mdc-top-app-bar', 'mdc-top-app-bar--fixed'],
didInsertElement: function () {
let component = this;
let componentJqueryObject = component.$();
let componentElement = componentJqueryObject[0];
let topAppBar = new mdc.topAppBar.MDCTopAppBar(componentElement);
let mainEl = document.getElementById('app-main');
topAppBar.setScrollTarget(mainEl);
............
Note: Its important to always use the didInsertHook for 2 reasons.
1.) According to Ember Docs, didInsert is at that point where the component's HTML is guaranteed to be inserted in the DOM.
2.) According to Fastboot Docs, didInsert is not executed in FastBoot mode which runs in Node yet MDC is a browser thing.
Enjoy!
EDIT: Based on Question below
import Component from '#ember/component';
import { get } from '#ember/object';
import $ from 'jquery';
export default Component.extend({
tagName: 'aside',
classNames: ['mdc-drawer', 'mdc-drawer--modal', 'app-drawer'],
didInsertElement: function () {
let component = this;
let componentJqueryObject = component.$();
let componentElement = componentJqueryObject[0];
let MDCDrawer = mdc.drawer.MDCDrawer;
let drawer = new MDCDrawer(componentElement);
$('header').on('click', '.drawer-menu', function() {
drawer.open = !drawer.open;
});
$('body').on('click', 'main', function() {
drawer.open = false;
});
$('aside').on('click', 'a', function() {
drawer.open = false;
});
// mdc web used to override the a href link element in the drawer component, causing all links to open with a page reload, use this hack if your version still does, assign every a href link a custom-link class and add data attributes to keep things the Ember way
let router = get(component, "router");
component.$().on("click" , ".custom-link" , function(e) {
e.preventDefault();
drawer.open = false;
let routeName = $(this).data("route");
let modelId = $(this).data("id");
if(modelId){
router.transitionTo(routeName , modelId);
} else {
router.transitionTo(routeName);
}
});
}
});

How to mock an Ember-CLI service in an acceptance test?

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');
});

How create a unit test for view in Ember CLI that renders the view (like tests for components)

I can't find a single example in google for unit test of views in Ember CLI that renders the view (without renders all app).
I wanna this for test events registered inside of didInserElement hook.
For components i can find docs very easy. For render the component in a test with moduleForComponent just do:
test("component test", function(){
var component = this.subject(),
element = this.append();
ok(element.is('.clickable'), 'has the clickable class');
});
But how i do this for views?
I use this way to render only the view in unit tests:
One important thing to note, is that you need to needs all templates, partials and helpers explicitly. Otherwise the test will fail due to lookup errors.
tests/unit/views/main-test.js:
import Ember from 'ember';
import { test, moduleFor } from 'ember-qunit';
var view;
moduleFor('view:main', 'MainView', {
needs: ['template:main'], // won't find the 'main' template without this
setup: function() {
var controller = Ember.ObjectController.extend({
//mockController if needs
}).create();
view = this.subject({
controller: controller,
templateName: 'main',
});
Ember.run(function() {
view.appendTo('#ember-testing');
});
},
teardown: function() {
Ember.run(function() {
view.destroy();
});
},
});
test("didInsertElement", function(){
var element = Ember.$('.main');
var controller = view.get('controller');
var eventForPressCtrlAltM = Ember.$.Event( "keydown", { which: 77, altKey: true, ctrlKey: true } );
Ember.run(function() {
element.trigger(eventForPressCtrlAltM);
});
strictEqual(/* ... */);
});

Ember.js - Integration Testing with Ember-CLI

I am following a Dockyard Tutorial on using ember-cli with rails. This particular section is on basic integration testing. Unfortunately, it doesn't seem to be registering properly (at least I don't think so). The test should fail and say something along the lines of "Expected: 'Welcome to Boston Ember'"; rather, it says "should pass jshint" which it is and is therefore passing. Any idea what I am doing wrong?
tests/integration/landing-page-test.js
import Ember from 'ember';
import startApp from 'bostonember/tests/helpers/start-app';
var App;
module('Integration - Landing Page', {
setup: function() {
App = startApp();
},
teardown: function() {
Ember.run(App, 'destroy');
}
});
test('Should welcome me to Boston Ember', function() {
visit('/').then(function() {
equal(find('h2#title').text(), 'Welcome bloopde bloopasa to Boston Ember');
});
});
tests/helpers/start-app.js
/* global require */
var Application = require('bostonember/app')['default'];
var Router = require('bostonember/router')['default'];
import Ember from 'ember';
export default function startApp(attrs) {
var App;
var attributes = Ember.merge({
// useful Test defaults
rootElement: '#ember-testing',
LOG_ACTIVE_GENERATION:false,
LOG_VIEW_LOOKUPS: false
}, attrs); // but you can override;
Router.reopen({
location: 'none'
});
Ember.run(function(){
App = Application.create(attributes);
App.setupForTesting();
App.injectTestHelpers();
});
App.reset(); // this shouldn't be needed, i want to be able to "start an app at a specific URL"
return App;
}
Will provide additional info upon request. Thank you!
Must have just been a hiccup?
Solution was to recreate the file, same code, but works now.

Initialized dependency not present when testing

I'm using ember-cli 0.0.35, and injecting a dependency onto my component via an initializer. It works great in development, but the property isn't present when I run tests. It appears that testing calls loadInitializers, but the dependency is not showing up on this.subject({});
I don't want to manually inject it for the tests. Is there a better way to handle this?
Initializer:
var FooServiceInitializer = {
name: 'foo',
initialize: function (container, application) {
application.inject('component:foo', 'foo', 'service:foo');
}
};
export default FooServiceInitializer;
Failing Test:
moduleForComponent('bar', 'Component: Bar', {
setup: function() {
App = startApp();
component = this.subject({});
},
teardown: function () {
Ember.run(App, App.destroy);
}
});
test('Properties: foo', function() {
// Make sure we injected the service
ok(component.foo, 'foo is injected');
});
As I said before, it really lends itself to an integration test since you are testing the container at this point (and not the mini container created by ic-ajax).
Your real test is along the lines of this
test("root lists 3 colors", function(){
var c = App.__container__.lookup('component:foo-bar');
ok(c.foo.blah);
});
If you feel guilty about using the container during testing (which you shouldn't) you can create a helper to avoid having to fix it all over the place when/if an api changes in the future.
Ember.Test.registerHelper('containerLookup',
function(app, look) {
return app.__container__.lookup(look);
}
);
Make sure you define this before
App.injectTestHelpers();
and then your test would look like
test("root lists 3 colors", function(){
var c = containerLookup('component:foo-bar');
ok(c.foo.blah);
});
http://emberjs.jsbin.com/doxigu/edit