I am writing unit test for testing the route, I have a login page with login button, I want to test that on click of my login button the page should navigate to dashboard page, but I am not sure how to do it.
here is few lines of code
import {
provide, DirectiveResolver, ViewResolver
}
from 'angular2/core';
import {
describe, expect, it, xit, inject, beforeEachProviders, beforeEach, injectAsync, TestComponentBuilder, setBaseTestProviders
}
from 'angular2/testing';
import {
TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS
}
from 'angular2/platform/testing/browser';
import {
Router, RouterOutlet, RouterLink, RouteParams, RouteData, Location, ROUTER_PRIMARY_COMPONENT
}
from 'angular2/router';
import {
RootRouter
}
from 'angular2/src/router/router';
import {
RouteRegistry
}
from 'angular2/src/router/route_registry';
import {
SpyLocation
}
from 'angular2/src/mock/location_mock';
import {
LoginComponent
}
from '../js/login.component';
import {
EnterpriseSearchComponent
}
from '../js/enterprise-search.component';
import {
AppConfig
}
from '../js/services/appconfig';
describe('login component', () => {
var location, router;
beforeEachProviders(() => {
return [
TEST_BROWSER_PLATFORM_PROVIDERS, TEST_BROWSER_APPLICATION_PROVIDERS,
provide(ROUTER_PRIMARY_COMPONENT, {
useValue: EnterpriseSearchComponent
}),
provide(Router, {
useClass: RootRouter
}), RouteRegistry,
provide(Location, {
useClass: SpyLocation
}), AppConfig
]
});
beforeEach(inject([Router, Location], (r, l) => {
router = r;
location = l;
}));
it('Should be able to navigate to Login page', (done) => {
router.navigate(['Login']).then(() => {
expect(location.path()).toBe('/login');
done();
}).catch(e => done.fail(e));
});
it('should validate login', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(LoginComponent).then((fixture) => {
fixture.detectChanges();
let compiled = fixture.debugElement.nativeElement;
let instance = fixture.componentInstance;
compiled.querySelector("#userid").value = 'dummyid';
compiled.querySelector("#passwrd").value = 'dummypassword';
fixture.detectChanges();
compiled = fixture.debugElement.nativeElement;
instance = fixture.componentInstance;
compiled.querySelector("#loginbtn").click();
fixture.detectChanges();
// here is where i want to test that the page is navigated to dashboard screen
});
}));
});
in the above code sample, inside last test spec I want to test the navigation
const element = fixture.nativeElement;
fixture.detectChanges();
expect(element.querySelectorAll('.dashboard-title')).toBe('Dashboard');
or a more clean way in my opinion is to
put your fixture into a field first i.e
return tcb.createAsync(LoginComponent).then((fixture) => {
this.myFixture = fixture;
...
Then you can validate it inside a different "it case" that if that page contains a specific element that only your dashboard page have
it('is this the login page?', () => {
const element = this.myFixture.nativeElement;
this.myFixture.detectChanges();
expect(element.querySelectorAll('.dashboard-title')).toBe('Dashboard');
});
Related
I am learning to write unit tests in ionic and am unable to write a test for AlertController. Below attached is the code
Terms.page.ts file
export class TermsPage {
constructor(private router: Router, private alertController: AlertController) {}
onAgreeClick() {
this.router.navigate(['/register']);
}
onDeclineClick() {
this.presentAlertConfirm();
}
async presentAlertConfirm() {
const alert = await this.alertController.create({
message: 'Please agree to our terms and conditions to be able to use this application!',
buttons: [
{
text: 'Agree',
cssClass: 'primary',
handler: () => {
this.onAgreeClick();
},
},
{
text: 'Maybe later',
role: 'cancel',
cssClass: 'secondry',
},
],
});
await alert.present();
}
}
Terms.spec.ts
import { DebugElement } from '#angular/core';
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { Router, RouterModule } from '#angular/router';
import { RouterTestingModule } from '#angular/router/testing';
import { EmptyTestComponent } from '#test-utils';
import { TermsPage } from './terms.page';
fdescribe('TermsConditionsComponent', () => {
let component: TermsPage;
let fixture: ComponentFixture<TermsPage>;
let de: DebugElement;
let router: Router;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [TermsPage],
imports: [
RouterModule.forRoot([]),
RouterTestingModule.withRoutes([{ path: '**', component: EmptyTestComponent }]),
],
}).compileComponents();
fixture = TestBed.createComponent(TermsPage);
component = fixture.componentInstance;
de = fixture.debugElement;
router = TestBed.inject(Router);
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
it('should be able to agree and go to registration on click', async () => {
const agreeButton = de.query(By.css('#button-agree')).nativeElement as HTMLIonButtonElement;
agreeButton.click();
await fixture.whenStable();
expect(router.url).toBe('/register');
});
it('should be able to trigger popup on click of disagree click', async () => {
const disagreeButton = de.query(By.css('#button-disagree')).nativeElement as HTMLIonButtonElement;
disagreeButton.click();
await fixture.whenStable();
expect(component.presentAlertConfirm).toBeTruthy();
});
});
I need to hit the 100% coverage
Would really appreciate it if someone could help me write test case to cover the alert button actions and present. Thanks in advance
Looks like you need to split your test into two:
test #1 for alertcontroller.create usage - to has been called with proper arguments
and test #2 for button handlers
First can be easely emulated with standard jasmine calls like .toHaveBeenCalledWith(...):
const alertControllerStub = jasmine.createSpyObj('AlertController', ['create']);
...
expect(alertControllerStub.create).toHaveBeenCalledWith(options);
And the second one, you need to fire "ok"/"cancel" manually and catch the method executed for both cases
const ({buttons}) = alertControllerStub.create.calls.first().args[0];
buttons[0].handler();
expect(smth_should_called_in_handler).toHaveBeenCalled();
afterEach(() => {
fixture.destroy();
});I am currently trying to write tests for my ngrx based angular 7 application. The problem is that my test fails with the error Uncaught TypeError: Cannot read property 'xxxx' of undefined thrown. Here's how my test file looks like.
explore-products.component.spec.ts
import { async, ComponentFixture, TestBed } from "#angular/core/testing";
import { ExploreProductsComponent } from "./explore-products.component";
import { provideMockStore, MockStore } from "#ngrx/store/testing";
import { IAppState } from "src/app/store/state/app.state";
import { Store, StoreModule } from "#ngrx/store";
import { appReducers } from "src/app/store/reducers/app.reducer";
describe("ExploreProductsComponent", () => {
let component: ExploreProductsComponent;
let fixture: ComponentFixture<ExploreProductsComponent>;
let store: MockStore<IAppState>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ExploreProductsComponent],
providers: [provideMockStore()],
imports: [StoreModule.forRoot(appReducers)]
});
store = TestBed.get(Store);
});
beforeEach(() => {
fixture = TestBed.createComponent(ExploreProductsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
expect(component).toBeTruthy();
});
});
The only should create test is throwing the error. The error is being throwing by the selector somehow, which means the xxxx property is not initialized but I am not sure how to resolve it. Here's what my component looks like.
explore-products.component.ts
import { Component, OnInit, OnDestroy } from "#angular/core";
import { IProduct } from "src/app/models/product";
import { environment } from "src/environments/environment";
import { selectProducts } from "../../store/selectors/product";
import { Store, select } from "#ngrx/store";
import { IAppState } from "src/app/store/state/app.state";
import { Subscription } from "rxjs";
#Component({
selector: "app-explore-products",
templateUrl: "./explore-products.component.html",
styleUrls: ["./explore-products.component.css"]
})
export class ExploreProductsComponent implements OnInit, OnDestroy {
public productsLoading = true;
public endpoint = environment.apiEndpoint;
private productsSelector = this.store.pipe(select(selectProducts));
public products: IProduct[];
private subscriptionsArr: Subscription[] = [];
constructor(private store: Store<IAppState>) {}
ngOnInit() {
this.subscriptions();
}
subscriptions() {
const subcriberProduct = this.productsSelector.subscribe(products => {
this.products = products;
if (this.products !== null) {
this.toggleLoadingSign(false);
}
});
this.subscriptionsArr.push(subcriberProduct);
}
toggleLoadingSign(toggleOption: boolean) {
this.productsLoading = toggleOption;
}
ngOnDestroy() {
for (const subscriber of this.subscriptionsArr) {
subscriber.unsubscribe();
}
}
}
Please let me know if I can provide any other information.
Update
The problem is with AppState. The error is thrown since the state is not initialized which causes the error to occur i.e state.xxxx is undefined. The error sometimes randomly doesn't occur. I am not sure how to fix this.
The same problem is also mentioned here. But no solution
For me adding this line of code afterEach(() => { fixture.destroy(); }); in all my spec files injecting provideMockStore() fixed this issue intermittently triggering.
describe('Component', () => {
let component: Component;
let fixture: ComponentFixture<Component>;
beforeEach(async(() => {
const state = { featureKey: { property: value } }; // The state of your feature snippet
TestBed.configureTestingModule({
providers: [
provideMockStore({ initialState: state }),
]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
afterEach(() => { fixture.destroy(); });
it('should create', () => {
expect(component).toBeTruthy();
});
});
For me adding:
afterEach(() => {
fixture.destroy();
});
to every spec file of a component solved the issue.
You can try something like this.
See how I've created mock store and used it. Added single line comments (with ************) wherever code was updated:
import { async, ComponentFixture, TestBed } from "#angular/core/testing";
import { ExploreProductsComponent } from "./explore-products.component";
import { provideMockStore, MockStore } from "#ngrx/store/testing";
import { IAppState } from "src/app/store/state/app.state";
import { Store, StoreModule } from "#ngrx/store";
import { appReducers } from "src/app/store/reducers/app.reducer";
describe("ExploreProductsComponent", () => {
let component: ExploreProductsComponent;
let fixture: ComponentFixture<ExploreProductsComponent>;
//Update the store def.************
let store: MockStore<any>;
beforeEach( async(() => { //*****************UPDATE
TestBed.configureTestingModule({
declarations: [ExploreProductsComponent],
providers: [provideMockStore()],
//Change to imports************
imports: [StoreModule.forRoot({})]
}).compileComponents();//*****************UPDATE
//Removed this
//store = TestBed.get(Store);************
}));//*****************UPDATE
beforeEach(() => {
fixture = TestBed.createComponent(ExploreProductsComponent);
//Get store instance************
store = fixture.debugElement.injector.get(Store);
component = fixture.componentInstance;
fixture.detectChanges();
});
it("should create", () => {
//Spy on store actions************
const spy = spyOn(store, 'dispatch');
//Test is store is called properly************
expect(spy).toHaveBeenCalledWith(Your params)
expect(component).toBeTruthy();
});
});
I'm creating a unit test for my Navbar Component and I'm getting an error:
Can't bind to 'routerLink' since it isn't a known property of 'a'
Navbar Component TS
import { Component } from '#angular/core';
import { Router } from '#angular/router';
import { NavActiveService } from '../../../services/navactive.service';
import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
#Component({
moduleId: module.id,
selector: 'my-navbar',
templateUrl: 'navbar.component.html',
styleUrls:['navbar.component.css'],
providers: [NavActiveService]
})
export class NavComponent {
showNavBar: boolean = true;
constructor(private router: Router,
private navactiveservice:NavActiveService,
private globalEventsManager: GlobalEventsManager){
this.globalEventsManager.showNavBar.subscribe((mode:boolean)=>{
this.showNavBar = mode;
});
}
}
Navbar Component Spec
import { ComponentFixture, TestBed, async } from '#angular/core/testing';
import { NavComponent } from './navbar.component';
import { DebugElement } from '#angular/core';
import { By } from '#angular/platform-browser';
import { Router } from '#angular/router';
export function main() {
describe('Navbar component', () => {
let de: DebugElement;
let comp: NavComponent;
let fixture: ComponentFixture<NavComponent>;
let router: Router;
// preparing module for testing
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NavComponent],
}).compileComponents().then(() => {
fixture = TestBed.createComponent(NavComponent);
comp = fixture.componentInstance;
de = fixture.debugElement.query(By.css('p'));
});
}));
it('should create component', () => expect(comp).toBeDefined());
/* it('should have expected <p> text', () => {
fixture.detectChanges();
const h1 = de.nativeElement;
expect(h1.innerText).toMatch(" ");
});*/
});
}
I realize that I need to add router as a spy, but if I add it as a SpyObj and declare it as a provider I get the same error.
Is there a better way for me to add fix this error?
EDIT: Working Unit Test
Built this unit test based on the answer:
import { ComponentFixture, TestBed, async } from '#angular/core/testing';
import { NavComponent } from './navbar.component';
import { DebugElement } from '#angular/core';
import { By } from '#angular/platform-browser';
import { RouterLinkStubDirective, RouterOutletStubComponent } from '../../../../test/router-stubs';
import { Router } from '#angular/router';
import { GlobalEventsManager } from '../../../services/GlobalEventsManager';
import { RouterModule } from '#angular/router';
import { SharedModule } from '../shared.module';
export function main() {
let comp: NavComponent;
let fixture: ComponentFixture<NavComponent>;
let mockRouter:any;
class MockRouter {
//noinspection TypeScriptUnresolvedFunction
navigate = jasmine.createSpy('navigate');
}
describe('Navbar Componenet', () => {
beforeEach( async(() => {
mockRouter = new MockRouter();
TestBed.configureTestingModule({
imports: [ SharedModule ]
})
// Get rid of app's Router configuration otherwise many failures.
// Doing so removes Router declarations; add the Router stubs
.overrideModule(SharedModule, {
remove: {
imports: [ RouterModule ],
},
add: {
declarations: [ RouterLinkStubDirective, RouterOutletStubComponent ],
providers: [ { provide: Router, useValue: mockRouter }, GlobalEventsManager ],
}
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(NavComponent);
comp = fixture.componentInstance;
});
}));
tests();
});
function tests() {
let links: RouterLinkStubDirective[];
let linkDes: DebugElement[];
beforeEach(() => {
// trigger initial data binding
fixture.detectChanges();
// find DebugElements with an attached RouterLinkStubDirective
linkDes = fixture.debugElement
.queryAll(By.directive(RouterLinkStubDirective));
// get the attached link directive instances using the DebugElement injectors
links = linkDes
.map(de => de.injector.get(RouterLinkStubDirective) as RouterLinkStubDirective);
});
it('can instantiate it', () => {
expect(comp).not.toBeNull();
});
it('can get RouterLinks from template', () => {
expect(links.length).toBe(5, 'should have 5 links');
expect(links[0].linkParams).toBe( '/', '1st link should go to Home');
expect(links[1].linkParams).toBe('/', '2nd link should go to Home');
expect(links[2].linkParams).toBe('/upload', '3rd link should go to Upload');
expect(links[3].linkParams).toBe('/about', '4th link should to to About');
expect(links[4].linkParams).toBe('/login', '5th link should go to Logout');
});
it('can click Home link in template', () => {
const uploadLinkDe = linkDes[1];
const uploadLink = links[1];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/');
});
it('can click upload link in template', () => {
const uploadLinkDe = linkDes[2];
const uploadLink = links[2];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/upload');
});
it('can click about link in template', () => {
const uploadLinkDe = linkDes[3];
const uploadLink = links[3];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/about');
});
it('can click logout link in template', () => {
const uploadLinkDe = linkDes[4];
const uploadLink = links[4];
expect(uploadLink.navigatedTo).toBeNull('link should not have navigated yet');
uploadLinkDe.triggerEventHandler('click', null);
fixture.detectChanges();
expect(uploadLink.navigatedTo).toBe('/login');
});
}
}
Just import RouterTestingModule in TestBed.configureTestingModule of your components spec.ts file
Eg:
import { RouterTestingModule } from '#angular/router/testing';
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [ ComponentHeaderComponent ]
})
The Angular Testing docs address this by using RouterLinkDirectiveStub and RouterOutletStubComponent so that routerLink is a known property of <a>.
Basically it says that using RouterOutletStubComponent is a safe way to test routerLinks without all the complications and errors of using the real RouterOutlet. Your project needs to know it exists so it doesn't throw errors but it doesn't need to actually do anything in this case.
The RouterLinkDirectiveStub enables you to click on <a> links with routerLink directive and get just enough information to test that it is being clicked (navigatedTo) and going to the correct route (linkParams). Any more functionality than that and you really aren't testing your component in isolation any more.
Take a look at their Tests Demo in app/app.component.spec.ts. Grab the testing/router-link-directive-stub.ts and add to your project. Then you will inject the 2 stubbed items into your TestBed declarations.
If you want only isolated test and DO NOT CARE about template,you can add NO_ERRORS_SCHEMA. This tells Angular not to show error if it encounters any unknown attribute or element in HTML
Eg:
TestBed.configureTestingModule({
declarations: [ ComponentHeaderComponent ],
schemas: [ NO_ERRORS_SCHEMA ]
})
I am trying to run unit tests on my component which is an output for my router. I have stubbed the router and service used by the component and I am trying to pull the element using the fixture.debugElement to confirm the tests are working. However this is always returning as NULL.
Tests
import { TestBed, async, ComponentFixture } from '#angular/core/testing';
import { Router } from '#angular/router';
import { By } from '#angular/platform-browser';
import { DebugElement } from '#angular/core';
import { HeroesComponent } from './heroes.component';
import { HeroService } from '../hero.service';
import { StubHeroService } from '../testing/stub-hero.service';
import { StubRouter } from '../testing/stub-router';
let comp: HeroesComponent;
let fixture: ComponentFixture<HeroesComponent>;
let de: DebugElement;
let el: HTMLElement;
describe('Component: Heroes', () => {
beforeEach( async(() => {
TestBed.configureTestingModule({
declarations: [HeroesComponent],
providers: [
{ provide: HeroService, useClass: StubHeroService },
{ provide: Router, useClass: StubRouter }
]
})
.compileComponents()
.then(() => {
fixture = TestBed.createComponent(HeroesComponent);
comp = fixture.componentInstance;
de = fixture.debugElement.query(By.css('*'));
console.log(de);
el = de.nativeElement;
});
}));
it('should create an instance', () => {
expect(comp).toBeTruthy();
});
it('should update the selected hero', () => {
comp.onSelect({
id: 0,
name: 'Zero'
});
fixture.detectChanges();
expect(el.querySelector('.selected').firstChild.textContent).toEqual(0);
});
});
Stubbed Router
export class StubRouter {
navigateByUrl(url: string) { return url; }
}
Before query the element call fixture.detectChanges
fixture = TestBed.createComponent(HeroesComponent);
comp = fixture.componentInstance;
//call detect changes here
fixture.detectChanges();
de = fixture.debugElement.query(By.css('*'));
console.log(de);
el = de.nativeElement;
I am using Enzyme to unit test my React components. I understand that in order to test the raw unconnected component I'd have to just export it and test it (I've done that). I have managed to write a test for the connected component but I am really not sure if this's the right way and also what exactly would I want to test for the connected component.
Container.jsx
import {connect} from 'react-redux';
import Login from './Login.jsx';
import * as loginActions from './login.actions';
const mapStateToProps = state => ({
auth: state.auth
});
const mapDispatchToProps = dispatch => ({
loginUser: credentials => dispatch(loginActions.loginUser(credentials))
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
Container.test.js
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
describe('Container Login', () => {
it('should render the container component', () => {
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
auth: {
sport: 'BASKETBALL'
}
});
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
});
This is an interesting question.
I usually do import both container and component to do the testing. For container testing I use, redux-mock-store. Component testing is for testing async functions. For instance in your case, login process is an async function using sinon stubs. Here is a snippet of the same,
import React from 'react';
import {Provider} from 'react-redux';
import {mount, shallow} from 'enzyme';
import {expect} from 'chai';
import LoginContainer from '../../src/login/login.container';
import Login from '../../src/login/Login';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { stub } from 'sinon';
const mockStore = configureMockStore([thunk]);
describe('Container Login', () => {
let store;
beforeEach(() => {
store = mockStore({
auth: {
sport: 'BASKETBALL',
},
});
});
it('should render the container component', () => {
const wrapper = mount(
<Provider store={store}>
<LoginContainer />
</Provider>
);
expect(wrapper.find(LoginContainer).length).to.equal(1);
const container = wrapper.find(LoginContainer);
expect(container.find(Login).length).to.equal(1);
expect(container.find(Login).props().auth).to.eql({ sport: 'BASKETBALL' });
});
it('should perform login', () => {
const loginStub = stub().withArgs({
username: 'abcd',
password: '1234',
});
const wrapper = mount(<Login
loginUser={loginStub}
/>);
wrapper.find('button').simulate('click');
expect(loginStub.callCount).to.equal(1);
});
});
As you pointed out, the way I usually do this is to export the un-connected component as well, and test that.
i.e.
export {Login};
Here's an example. Source of the component, and source of the tests.
For the wrapped component, I don't author tests for those because my mappings (mapStateToProps and mapDispatchToProps) are generally very simple. If I wanted to test a wrapped component, I'd really just be testing those maps. So those are what I would choose to explicitly test, rather than re-testing the entire component in a wrapped form.
There are two ways to test those functions. One way would be to export the functions within the module itself.
i.e.;
export {mapStateToProps, mapDispatchToProps}
I'm not a huge fan of this, because I wouldn't want other modules in the app to access them. In my tests, I sometimes use babel-plugin-rewire to access "in-scope" variables, so that's what I would do in this situation.
That might look something like:
import {
Login, __Rewire__
}
const mapStateToProps = __Rewire__.__get__('mapStateToProps');
describe('mapStateToProps', () => { ... });
If we have a router issue, we can consider to add the router lib into the test file, eg:
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import { mount } from 'enzyme';
import ReadDots from './ReadDots';
const storeFake = state => ({
default: () => {
},
subscribe: () => {
},
dispatch: () => {
},
getState: () => ({ ...state })
});
const store = storeFake({
dot: {
dots: [
{
id: '1',
dot: 'test data',
cost: '100',
tag: 'pocket money'
}
]
}
});
describe('<ReadDots />', () => {
it('should render ReadDots component', () => {
const component = mount(
<Provider store={store}>
<Router>
<ReadDots />
</Router>
</Provider>
);
expect(component.length).toEqual(1);
});
});