Unit testing in angular2, dependency injection - unit-testing

Starting out with angular 2 after spending time with angular 1. Not having unit tested this much as it's more of a side project thing, I'm trying at least start out OK... I started with the example from AngularClass if that makes a difference.
Struggling in app.component.ts already, which contains my navigation bits. Relevant bits of the template here:
<nav class="navbar navbar-light bg-faded">
<div class="container">
<div class="nav navbar-nav">
<a class="navbar-brand" [routerLink]=" ['./'] ">Navbar</a>
<loading class="nav-item nav-link pull-xs-right" [visible]="user === null"></loading>
</div>
</div>
</nav>
<div class="container">
<main>
<router-outlet></router-outlet>
</main>
</div>
<footer>
<hr>
<div class="container">
</div>
</footer>
Component itself does not contain much:
import { Component, ViewEncapsulation } from '#angular/core';
import { AuthService } from './_services';
import { User } from './_models';
import { Loading } from './_components';
#Component({
selector: 'app',
encapsulation: ViewEncapsulation.None,
template: require('./app.component.html'),
styles: [
require('./app.style.css')
]
})
export class App {
user: User;
constructor(private auth: AuthService) {
}
ngOnInit() {
this.auth.getUser().subscribe(user => this.user = user);
}
}
All modules, components and routes are bootstrapped through the App module. Can post if required.
The test I'm having to write for it has me hooking up basically everything from the router (so it seems). First, [routerLink] is not a native attribute of 'a'. Ok, I fix it. Then:
Error in ./App class App - inline template:3:6 caused by: No provider for Router!
So, I hook up router, only to find:
Error in ./App class App - inline template:3:6 caused by: No provider for ActivatedRoute!
Which I added, to find out that:
Error in ./App class App - inline template:3:6 caused by: No provider for LocationStrategy!
By now, the test looks like:
import { inject, TestBed, async } from '#angular/core/testing';
import { AuthService } from './_services';
import { Router, RouterModule, ActivatedRoute } from '#angular/router';
import { AppModule } from './app.module';
// Load the implementations that should be tested
import { App } from './app.component';
import { Loading } from './_components';
describe('App', () => {
// provide our implementations or mocks to the dependency injector
beforeEach(() => TestBed.configureTestingModule({
declarations: [App, Loading],
imports: [RouterModule],
providers: [
{
provide: Router,
useClass: class {
navigate = jasmine.createSpy("navigate");
}
}, {
provide: AuthService,
useClass: class {
getAccount = jasmine.createSpy("getAccount");
isLoggedIn = jasmine.createSpy("isLoggedIn");
}
}, {
provide: ActivatedRoute,
useClass: class { }
}
]
}));
it('should exist', async(() => {
TestBed.compileComponents().then(() => {
const fixture = TestBed.createComponent(App);
// Access the dependency injected component instance
const controller = fixture.componentInstance;
expect(!!controller).toBe(true);
});
}));
});
I'm already mocking the inputs, this seems wrong to me. Am I missing something? Is there a smarter way of loading the whole app on a test, instead of bolting in every single dependency, all the time?

For testing, you should use the RouterTestingModule instead of the RouterModule. If you want to add routes you can use withRoutes
imports: [
RouterTestingModule.withRoutes(Routes) // same any normal route config
]
See Also
Angular 2 unit testing components with routerLink
Second half of this post for an idea of mock the ActivatedRoute. Sometimes you don't want the whole routing facility when unit testing. You can just mock the route.

Related

Why does shallow Rendering not work as expected?

I am using enzyme for test my create-react-app component, but It did not work intuitively.
Am I misunderstanding what shallow rendering is?
import React from "react";
import { Card } from "./Card";
const TestUser = () => {
return (
<div>
<div className="test" />
<div className="wrapper">
<Card />
<Card />
<Card />
</div>
</div>
);
};
export default TestUser;
.test.js
import React from "react";
import TestUser from "./TestUser";
import { shallow } from "enzyme";
import { Card } from "./Card";
it("should render right", () => {
const component = shallow(<TestUser />);
expect(component.find(Card)).to.have.length(3);
});
I expect it should pass the test, cause it does have 3 Card components in TestUser
But it output: TypeError: Cannot read property 'have' of undefined
How does that work?
I have the same problem. It solved by using the below .
I have created a jest.config.js file at the root level and add below code.
module.export ={
setupFiles:[ '<rootDir>/src/setUpTests.js']
}
I have created setUpTests.js file and add below code.
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
And solved my problem.
Try this out. You have to give it as a string literal. Also to me the expect library you are using is not the one you get from chai and may have different set of helper methods, hence giving that error. Unfortunately I don't have the code with me to check further. Nothing wrong with shallow rendering at all.
import React from "react";
import TestUser from "./TestUser";
import { shallow } from "enzyme";
import { expect } from 'chai';
it("should render right", () => {
const component = shallow(<TestUser />);
expect(component.find('Card')).to.have.length(3);
});
Also you don't need to have this statement here, import Card from "./card";
In the TestUser component change the import statement like this.
import Card from "./card";
So now your TestUser component should look like this.
import React from "react";
import Card from "./card";
const TestUser = () => {
return (
<div>
<div className="test" />
<div className="wrapper">
<Card />
<Card />
<Card />
</div>
</div>
);
};
export default TestUser;
Use the following command to install chai library.
npm install --save chai
If you really want to use Jest change your assertion as below.
import React from "react";
import TestUser from "./testuser";
import { shallow } from "enzyme";
it("should render right", () => {
const component = shallow(<TestUser />);
expect(component.find('Card')).toHaveLength(3);
});
Personally I like mocha due to it's fluent API.
Hope this helps. Happy coding !
Shallow rendering won't go as deep as you want in this case because of your div nesting.
http://airbnb.io/enzyme/docs/api/shallow.html
Either use mount or use .dive() API to go one level further. See the example in the enzyme docs:
http://airbnb.io/enzyme/docs/api/ShallowWrapper/dive.html
it("should render right", () => {
const component = shallow(<TestUser />);
const element = component.find('wrapper');
chai.expect(element.props.children).to.have.length(3);
});
Use ().toEqual() instead of .to.have.length() as .to.have is not any function in jest expect library
Visit here for more info
import React from "react";
import TestUser from "./testuser";
import { shallow } from "enzyme";
it("should render right", () => {
const component = shallow(<TestUser />);
expect(component.find('Card').length).toEqual(3);
});

Testing route navigation without an outlet in Angular

I am writing a spec for an Angular component that displays a button that will navigate to another page. The component makes use of Router::navigate() but does not itself have a router outlet. A parent component has the outlet. In my spec, the test should confirm that clicking on the button routes to the correct path.
My current (broken) spec tries to use RouterTestingModule to provide a route to a DummyComponent. When the button is clicked in the spec I get the following error:
'Unhandled Promise rejection:', 'Cannot find primary outlet to load 'DummyComponent'', '; Zone:', 'angular', '; Task:', 'Promise.then', '; Value:', Error{__zone_symbol__error: Error{originalStack: 'Error: Cannot find primary outlet to load 'DummyComponent'
Obviously I am approaching this problem in the wrong manner. What is the correct way to test router navigation when the component does not have a router outlet?
The component (pseudo-code):
#Component({
template: `
Go to the <button (click)="nextPage">next page</button>
`
})
export class ExampleComponent {
public myId = 5;
constructor(private _router: Router);
public nextPage(): void {
this._router.navigate(['/example', this.myId]);
}
}
The spec. This does not work:
const FAKE_ID = 999;
describe('ExampleComponent Test', () => {
let exampleComponent: ExampleComponent;
let fixture: ComponentFixture<ExampleComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ DummyComponent ],
imports: [
RouterTestingModule.withRoutes([
{ path: 'example/:id', component: DummyComponent }
]);
]
});
fixture = TestBed.createComponent(exampleComponent);
exampleComponent = fixture.componentInstance;
});
it('should route to example/:id', inject([Router, Location], (router: Router, location: Location) => {
fixture.detectChanges();
exampleComponent.myId = FAKE_ID;
const LINK_BUTTON = fixture.debugElement.query(By.css('button'));
LINK_BUTTON.nativeElement.dispatchEvent(new Event('click'));
expect(location.path()).toEqual('/example/' + FAKE_ID);
});
});
There needs to be an outlet (<router-outlet>) for the DummyComponent. If the DummyComponent is a route being navigated to from the ExampleComponent, then the ExampleComponent should have the outlet. You also also need to add the ExampleComponent to the declarations`
#Component({
tempalte: `
<router-outlet></router-outlet>
<button (click)="nextPage">next page</button>
`
})
class ExampleComponent{}
declarations: [ ExampleComponent, DummyComponent ]
If you want to avoid having to set up this infrastructure just to test the route being navigated to, the better option might be to just mock the Router, and just check that the navigate method is called with the correct path.
beforeEach(()=>{
TestBed.configureTestingModule({
providers: [
{
provide: Router,
useValue: { navigate: jasmine.createSpy('navigate') }
}
]
})
})
With this, you don't need to configure an routing at all, as you're using a fake Router. Then in your test
it('should route to example/:id', inject([Router], (router: Router) => {
expect(router.navigate).toHaveBeenCalledWith(['/example', FAKE_ID]);
});

Cannot read property 'contextTypes' of undefined Unit Testing enzyme

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 { ...

Unit test for a component with dependency which has dependencies, what's wrong?

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

Angular 2 access directive from child template element

I have a problem with angular 2 for two days and I came to get some help from you guys.
The problem is when I try to access a directive child declared within a with ContentChild or ContentChildren from a parent component the Angular doesn't find any child. If I don't use template tag my parent component find the children without a problem.
Plunk
The parent ngAfterContentInit and ngAfterViewInit show that children QueryList has no results
My code is:
import { Component } from '#angular/core';
import {ParentComponent} from './components/parent.component';
import {ChildDirective} from './components/child.directive';
#Component({
selector: 'my-app',
template: `
<parent>
<template>
<button child>button 1 with child directive</button>
<button child>button 2 with child directive</button>
</template>
</parent>
`,
directives: [ParentComponent, ChildDirective]
})
export class AppComponent { }
The Parent Component:
import {Component, ContentChild, ContentChildren, QueryList,
TemplateRef, AfterContentInit, AfterViewInit} from '#angular/core';
import {ChildDirective} from './child.directive';
#Component({
selector: 'parent',
template: `
Parent Template
<br/>
<br/>
<template [ngTemplateOutlet]="template"></template>
`
})
export class ParentComponent implements AfterContentInit, AfterViewInit{
#ContentChild(TemplateRef)
template:TemplateRef;
#ContentChildren(ChildDirective)
children:QueryList<ChildDirective>;
ngAfterContentInit(){
console.log(this.children);
}
ngAfterViewInit(){
console.log(this.children);
}
}
The Child Directive
import {Directive, OnInit, HostListener, EventEmitter, Output} from '#angular/core';
#Directive({
selector: '[child]',
})
export class ChildDirective implements OnInit{
ngOnInit(){
console.log('child started');
}
#Output()
onClick:EventEmitter = new EventEmitter;
#HostListener('click')
onMouseEnter() {
console.log('clicked');
this.onClick.emit('the child has been clicked.');
}
}
Well, that's it, I wait some help cause I don't know what to do to solve this.
Thanks