I have a simple Angular2 component that consists of the following
import { Component, OnInit, Input } from '#angular/core';
import {FooterLinksService} from './footer-links.service';
import { environment } from '../../environments/environment';
#Component({
selector: 'app-footer-links',
templateUrl: './footer-links.component.html',
styleUrls: ['./footer-links.component.css'],
providers: [FooterLinksService]
})
export class FooterLinksComponent implements OnInit {
constructor(private footerLinksService: FooterLinksService) {
let footerLinks = this.footerLinksService.LoadFooterLinks();
}
}
I am trying to write the unit tests with Jasmine for this. Now I want to mock the FooterLinksService, but most of the examples I have seen involve manually writing a FooterLinksServiceMock. Is there any other approach I can use which autogenerates the mock service like NSubStitute and I provide the expected returns values from footerLinksService.LoadFooterLinks
As mentioned by #JBNizet, you could just use a spy. What you would do is get the actual service inside the test, then you can spy on a method an return any arbitrary value, when that method is called. It might look something like
describe('component: FooterLinksComponent', () => {
let fixture;
let service;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
FooterLinksComponent
],
});
fixture = TestBed.createComponent(FooterLinksComponent);
service = fixture.debugElement.injector.get(FooterLinksService);
});
it('should change message returned', () => {
spyOn(service, 'LoadFooterLinks').and.returnValue('Hello new message');
fixture.detectChanges();
expect(fixture.componentInstance.message).toBe('Hello new message');
});
});
You will need to move the code where you access the service from inside the constructor into the ngOnInit. The reason for this is because
You are using #Component.providers, so the service will not be created until the component is created.
When the component is created, the constructor is already called. So this doesn't give time for you to set up the spy. When you use ngOnInit, the ngOnInit is not called until you call fixture.detectChanges()
Related
I have a component that calls something like:
this.get('store')
.findAll('calendar-event')
.then((data) => {
// do stuff
});
However, when I replace findAll() with query() it breaks my integration testing.
this.get('store')
.query('calendar-event', { some_stuff: [596] })
.then((data) => {
// do stuff
});
The application in the web browser via ember server continues to function correctly. However: no luck with integration testing. Error I get is: TypeError: Cannot read property 'setObjects' of null
Integration test looks like:
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import moment from 'moment';
import startMirage from '../../helpers/setup-mirage-for-integration';
moduleForComponent('main-calendar', 'Integration | Component | main calendar', {
integration: true,
beforeEach() {
startMirage(this.container);
},
afterEach() {
window.server.shutdown();
}
});
test('it renders', function (assert) {
this.render(hbs`{{main-calendar}}`);
assert.equal(this.$('.col-sm.text-center').text().trim(), moment().format('MMMM YYYY'));
});
Any ideas why it would render with the server but not on the integration testing side of things.
I'm trying unit testing in my react using redux app. So I need to test connected components unfortunately i got this error:
Cannot read property 'contextTypes' of undefined
I use enzyme in unit testing Here is my component:
import React from 'react';
import TextFieldGroup from '../common/TextFieldGroup';
import validateInput from '../../server/validations/login';
import { connect } from 'react-redux';
import { login } from '../../actions/authActions';
class LoginForm extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
errors: {},
isLoading: false
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
}
isValid() {
const { errors, isValid } = validateInput(this.state);
if (!isValid) {
this.setState({ errors });
}
return isValid;
}
onSubmit(e) {
e.preventDefault();
if (this.isValid()) {
this.setState({ errors: {}, isLoading: true });
this.props.login(this.state).then(
(res) => this.context.router.push('/'),
(err) => this.setState({ errors: err.response.data.errors, isLoading: false })
);
}
}
onChange(e) {
this.setState({ [e.target.name]: e.target.value });
}
render() {
const { errors, username, password, isLoading } = this.state;
return (
<form onSubmit={this.onSubmit}>
<h1>Login</h1>
{ errors.form && <div className="alert alert-danger">{errors.form}</div> }
<TextFieldGroup
field="username"
label="Username"
value={username}
error={errors.username}
onChange={this.onChange}
/>
<TextFieldGroup
field="password"
label="Password"
value={password}
error={errors.password}
onChange={this.onChange}
type="password"
/>
<div className="form-group"><button className="btn btn-primary" disabled={isLoading}>Login</button></div>
</form>
);
}
}
LoginForm.propTypes = {
login: React.PropTypes.func.isRequired
}
LoginForm.contextTypes = {
router: React.PropTypes.object.isRequired
}
export default connect(null, { login })(LoginForm);
Here is my test:
import React from 'react';
import { mount, shallow } from 'enzyme';
import {expect} from 'chai';
import sinon from 'sinon';
import { connect } from 'react-redux'
import { Login } from '../../js/react/components/login/LoginForm';
describe('<Login />', function () {
it('should have an input for the username', function () {
const wrapper = shallow(<Login />);
expect(wrapper.find('input[name=username]')).to.have.length(1);
});
it('should have an input for the password', function () {
const wrapper = shallow(<Login />);
expect(wrapper.find('input[name=password]')).to.have.length(1);
});
it('should have a button', function () {
const wrapper = shallow(<Login />);
expect(wrapper.find('button')).to.have.length(1);
});
it('simulates click events', () => {
const onButtonClick = sinon.spy();
const wrapper = shallow(
<Login onButtonClick={onButtonClick} />
);
wrapper.find('button').simulate('click');
expect(onButtonClick).to.have.property('callCount', 1);
});
});
Suggestions and answers are highly appreciated :)
Testing a decorated component
To test the component directly you need to export just the component itself function without passing it into Connect.
Currently, in your test you are importing the wrapper component returned by connect(), and not the LoginForm component itself. This is fine if you want to test the interaction of the LoginForm component with Redux. If you are not testing the component in isolation then this method of exporting and importing the component is fine. Howver you need to remember to wrap the component in your test with a a component which is created specifically for this unit test. Let's now look at this case where we are testing a connected component and explain why we are wrapping it in in our unit tests.
The relationship between Provider and Connect components in react-redux
The react-redux library gives us a Provider component. The purpose of Provider is to allow any of its child components to access the Redux store when wrapped in a Connect component. This symbiotic relationship between Provider and Connect allow any component which is wrapped in the Connect component to access the Redux store via React's context feature.
Testing the connected component
Remember that a connected component is a component that is wrapped in a Connect component and this wrapping gives our component access to the Redux store? For this reason we need to create a mock store in our test file since we need a store in order to test how the component interacts with it.
Giving our Connected Component a store with Provider in tests
However Connect doesn't know how to magically access the store. It needs to be nested (wrapped) in a component. The Provider component gives our component wrapped in Connect access to the Store by hooking into Provider via React's context api.
As we can see Provider takes a prop consisting of the Redux store:
ReactDOM.render(
<Provider store={store}>
<MyRootComponent />
</Provider>,
rootEl
)
So to test the connected component you need to wrap it with Provider. Remember Provider takes the Redux store object as a prop. For our tests we can use the redux-mock-store library which helps us set up a mock store and gives us some methods on the store to track which actions have been dispatched if we need them.
import { Provider } from 'react-redux'
import { mount } from 'enzyme'
import chai, {expect} from 'chai'
import chaiEnzyme from 'chai-enzyme'
import configureMockStore from 'redux-mock-store'
chai.use(chaiEnzyme())
import ConnectedLoginForm, from '../app.js'
const mockStore = configureMockStore(middlewares)
describe.only('<LoginForm Component />', () => {
it('LoginForm should pass a given prop to its child component' () => {
const store = mockStore(initialState)
const wrapper = mount(
<Provider store={store}>
<ConnectedLoginForm />
</Provider>
)
expect(wrapper.type()).to.equal('div')
})
})
But sometimes you want to test just the rendering of the component, without a Redux store. Lets look at this case where we want to test the rendering of the unconnected LoginForm component in isolation.
Testing the unconnected component
So currently you are testing the new component that is created by wrapping the original LoginForm component with Connect.
In order to test the original component itself without connect, create a second export declaration solely for the LoginForm component.
import { connect } from 'react-redux'
// Use named export for unconnected component (for tests)
export class App extends Component { /* ... */ }
// Use default export for the connected component (for app)
export default connect(mapStateToProps)(App)
You can now test rendering of the component without the Redux store.
Importing the components in tests
Remember when you export the component in this way -
Default exports for importing connected component
Named exports for
importing unconnected component
Now in your test file import the undecorated LoginForm component like this:
// Note the curly braces: grab the named export instead of default export
import { LoginForm } from './App'
Or import both undocarated and decorated (connected) components:
import ConnectedLoginForm, { LoginForm } from './App'
If you want to test how the LoginForm component interacts with your Redux store you must
You didn't export your Login component. Meaning:
class LoginForm extends React.Component { ... -> export class LoginForm extends React.Component { ...
I'm trying to get the basics of Angular2 test API and TestBed.compileComponents() is driving me nuts. Either I call it like this:
beforeEach( done => {
TestBed.configureTestingModule({
declarations: [MyComponent]
})
.compileComponents().then( () => {
fixture = TestBed.createComponent(MyComponent);
component = fixture.componentInstance();
});
done();
});
And then my component is undefined in my test (I believe since compileComponent is async, test is run before my var component gets a value)
Either like that (as describe in documentation):
beforeEach( async(() => {
TestBed.configureTestingModule({
declarations: [MyComponent]
}))
.compileComponents();
beforeEach( () => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance();
});
But then I get the error: This test module uses the component HomeComponent which is using a "templateUrl", but they were never compiled. Please call "TestBed.compileComponents" before your test.
Can anybody help on this ?
Forget to say I use webpack and RC6
Try this:
describe('FooComponent', function () {
let fixture: ComponentFixture<FooComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FooComponent]
});
fixture = TestBed.createComponent(FooComponent);
fixture.detectChanges();
});
You don't need asynchronicity here.
Try this:
beforeEach( async( () => {
TestBed.configureTestingModule({
declarations: [MyComponent]
})
.compileComponents().then( () => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance();
});
}));
I faced the same issue. I think the problem is in your component template. If you use some other custom components, but didn't specify them in testing module, than Angular throws that error (of course very misleading).
So, you next options:
Specify all used components in TestBed configuration
As variation of that you may stub all components by corresponding mocks.
use NO_ERRORS_SCHEMA for shallow testing as described here https://angular.io/docs/ts/latest/guide/testing.html#shallow-component-test
Just to build on the answer by Zonkil I found to get this to work I had to actually set the fixture and create the component within the test. For example:
it('test the component renders',() => {
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
expect(comp).toBeDefined();
});
I have annoying error that probably by my mistake I cannot resolve.
I have on simple component which is actually nothing else than a top-bar element in my web application.
This component as you can see has only one dependency, the UserService and it uses it quite simply:
import { Component, OnInit } from '#angular/core';
import { MdButton } from '#angular2-material/button';
import { MdIcon , MdIconRegistry} from '#angular2-material/icon';
import { UserService } from '../services/index';
import { RouteConfig, ROUTER_DIRECTIVES, Router, ROUTER_PROVIDERS
} from '#angular/router-deprecated';
#Component({
moduleId: module.id,
selector: 'top-bar',
templateUrl: 'top-bar.component.html',
styleUrls: ['top-bar.component.css'],
providers: [MdIconRegistry, UserService, ROUTER_PROVIDERS],
directives: [MdButton, MdIcon, ROUTER_DIRECTIVES]
})
export class TopBarComponent implements OnInit {
constructor(private userService: UserService) {
this.userService = userService;
}
ngOnInit() {
}
/**
* Call UserService and logout() method
*/
logout() {
this.userService.logout();
}
}
As this service has also some dependencies (router etc) I had to provide them at the beforeEachProviders method as you can see:
import {
beforeEach,
beforeEachProviders,
describe,
expect,
it,
inject,
} from '#angular/core/testing';
import { TopBarComponent } from './top-bar.component';
import {
Router, RootRouter, RouteRegistry, ROUTER_PRIMARY_COMPONENT
} from '#angular/router-deprecated';
import { provide } from '#angular/core';
import { SpyLocation } from '#angular/common/testing';
import { UserService } from '../services/index';
describe('Component: TopBar', () => {
beforeEachProviders(() => [
RouteRegistry,
provide(Location, { useClass: SpyLocation }),
provide(ROUTER_PRIMARY_COMPONENT, { useValue: TopBarComponent }),
provide(Router, { useClass: RootRouter }),
UserService,
TopBarComponent
]);
it('should inject the component', inject([TopBarComponent],
(component: TopBarComponent) => {
expect(component).toBeTruthy();
}));
});
When I run the test though I get this error message:
Chrome 51.0.2704 (Mac OS X 10.11.5) Component: TopBar should inject the component FAILED
Error: No provider for Location! (TopBarComponent -> UserService -> Router -> Location)
Error: DI Exception[......]
First of all as you can see the Location provider is provided.
And secondary, why my test requires to provide (or inject) also the dependencies of the used into the tested component service?
For example if from the above test I remove the Router the even that my component doesn't use Router I'll get an error because the used service does. Then shouldn't I received the same error in the component and not only in the test?
UPDATE - CHANGE OF CODE & ERROR MESSAGE
I have manage to stop getting this error by changing my spec doe to this:
import {
beforeEach,
describe,
expect,
it,
} from '#angular/core/testing';
import { TopBarComponent } from './top-bar.component';
import { UserService } from '../services/index';
import {
Router
} from '#angular/router-deprecated';
import { Http } from '#angular/http';
import { AuthHttp } from 'angular2-jwt';
describe('Component: TopBar', () => {
let router: any = Router;
let authHttp: any = AuthHttp;
let http: any = Http;
let component: TopBarComponent;
let service: UserService = new UserService(router, authHttp, http);
beforeEach(() => {
component = new TopBarComponent(service);
});
it('logout function should work ', () => {
let logout = component.logout;
logout();
expect(localStorage.getItem('token')).toBe(null);
});
});
But know I'm getting this error from my component:
TypeError: Cannot read property 'userService' of undefined
The mentioned error is on this function:
logout() {
this.userService.logout();
}
of my component but this is only on the test. In app it works normally. The function cannot reach constructor's parameter for some reason in my test.
kind of stack here...
By your code I understand that you are trying to test the topbar component.
Top bar component has a dependency on UserService.
So to answer your question, Angular does dependency injection when you run your application because all the providers are configured in the module file. But when you try to test the code in spec file you have to configure the testbed with all providers, components in the beforeEach method that are going to be used and angular leaves all the responsibility of resolving the dependency to the user as testbed acts as environment to run your code.
In your code you can do something like this
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [UserService, any other service on which user service is dependent] });
});
Here you can see TestBed.configureTestingModule method creates a dummy module to aid in running ur test case.
My suggestion will be create a mock UserService which doesn't have any other dependencies like the original one and assign it in the provider
Something like this
export MockUserService {
Put all essential methods stub here.
}
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provide: UserService, useClass: MockUserService] });
});
Then just test the topBar component's usecases.
Try creating the object of service inside beforeEach using TestBed.get(UserService). This code will automatically resolve the dependencies and create that object for use.
Remove '= new UserService(router, authHttp, http);' from 'let service: UserService = new UserService(router, authHttp, http);'
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');
});