How to test Angular2's router.navigate? - unit-testing

I've run into missing <router-outlet> messages in other unit tests, but just to have a nice isolated example, I created an AuthGuard that checks if a user is logged in for certain actions.
This is the code:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
if (!this.authService.isLoggedIn()) {
this.router.navigate(['/login']);
return false;
}
return true;
}
Now I want to write a unit test for this.
This is how I start my test:
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule.withRoutes([
{
path: 'login',
component: DummyComponent
}
])
],
declarations: [
DummyComponent
],
providers: [
AuthGuardService,
{
provide: AuthService,
useClass: MockAuthService
}
]
});
});
I created a DummyComponent that does nothing.
Now my test. Pretend that the service returns false and that it triggers this.router.navigate(['/login']):
it('should not let users pass when not logged in', (): void => {
expect(authGuardService.canActivate(<any>{}, <any>{})).toBe(false);
});
This will throw an exception with "Cannot find primary outlet to load".
Obviously I can use toThrow() instead of toBe(false), but that doesn't seem like a very sensible solution. Since I'm testing a service here, there is no template where I can put the <router-outlet> tag. I could mock the router and make my own navigate function, but then what's the point of RouterTestingModule? Perhaps you even want to check that navigation worked.

I could mock the router and make my own navigate function, but then what's the point of RouterTestingModule? Perhaps you even want to check that navigation worked.
There's no real point. If his is just a unit test for the auth guard, then just mock and spy on the mock to check that it's navigate method was called with the login argument
let router = {
navigate: jasmine.createSpy('navigate')
}
{ provide: Router, useValue: router }
expect(authGuardService.canActivate(<any>{}, <any>{})).toBe(false);
expect(router.navigate).toHaveBeenCalledWith(['/login']);
This is how unit tests should normally be written. To try to test any actual real navigation, that would probably fall under the umbrella of end-to-end testing.

If you want to test the router without mocking it you can just inject it into your test and then spy directly on the navigate method there. The .and.stub() will make it so the call doesn't do anything.
describe('something that navigates', () => {
it('should navigate', inject([Router], (router: Router) => {
spyOn(router, 'navigate').and.stub();
expect(authGuardService.canActivate(<any>{}, <any>{})).toBe(false);
expect(router.navigate).toHaveBeenCalledWith(['/login']);
}));
});

this worked for me
describe('navigateExample', () => {
it('navigate Example', () => {
const routerstub: Router = TestBed.get(Router);
spyOn(routerstub, 'navigate');
component.navigateExample();
});
});

it(`editTemplate() should navigate to template build module with query params`, inject(
[Router],
(router: Router) => {
let id = 25;
spyOn(router, "navigate").and.stub();
router.navigate(["/template-builder"], {
queryParams: { templateId: id }
});
expect(router.navigate).toHaveBeenCalledWith(["/template-builder"], {
queryParams: { templateId: id }
});
}
));

I came up with something like that:
describe('TestComponent', () => {
let component: TestComponent;
let router: Router;
let fixture: ComponentFixture<TestComponent>;
const routerSpy = jasmine.createSpyObj('Router', ['navigate']); // create a router spy
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
HttpClientTestingModule
],
declarations: [TestComponent],
providers: [
{ provide: Router, useValue: routerSpy } // use routerSpy against Router
],
}).compileComponents();
}));
beforeEach(() => {
router = TestBed.inject(Router); // get instance of router
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it(`should navigate to 'home' page`, () => {
component.navigateToHome(); // call router.navigate
const spy = router.navigate as jasmine.Spy; // create the navigate spy
const navArgs = spy.calls.first().args[0]; // get the spy values
expect(navArgs[0]).toBe('/home');
});
});
Inspired with angular docs: https://angular.io/guide/testing-components-scenarios#routing-component

I am new to unit testing angular/javascript apps. I needed a way to mock (or spy) for my unit test. The following line is borrowed from Experimenter and helped me TREMENDOUSLY!
const routerSpy = jasmine.createSpyObj('Router', ['navigate']); // create a router spy
I would like to say that I had no idea I could do that with Jasmine. Using that line above, allowed me to then create a spy on that object and verify it was called with the correct route value.
This is a great way to do unit testing without the need to have the testbed and all the ceremony around getting the testing module setup. Its also great because it still allows me to have a fake router object with out the need to stub in all of the parameters, methods, etc etc etc.

Related

Angular 2: How to mock ChangeDetectorRef while unit testing

I have just started with Unit-Testing, and I have been able to mock my own services and some of Angular and Ionic as well, but no matter what I do ChangeDetectorRef stays the same.
I mean which kind of sorcery is this?
beforeEach(async(() =>
TestBed.configureTestingModule({
declarations: [MyComponent],
providers: [
Form, DomController, ToastController, AlertController,
PopoverController,
{provide: Platform, useClass: PlatformMock},
{
provide: NavParams,
useValue: new NavParams({data: new PageData().Data})
},
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock}
],
imports: [
FormsModule,
ReactiveFormsModule,
IonicModule
],
})
.overrideComponent(MyComponent, {
set: {
providers: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
],
viewProviders: [
{provide: ChangeDetectorRef, useClass: ChangeDetectorRefMock},
]
}
})
.compileComponents()
.then(() => {
let fixture = TestBed.createComponent(MyComponent);
let cmp = fixture.debugElement.componentInstance;
let cdRef = fixture.debugElement.injector.get(ChangeDetectorRef);
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
})
));
it('fails no matter what', async(() => {
spyOn(cdRef, 'markForCheck');
spyOn(cmp.cdRef, 'markForCheck');
cmp.ngOnInit();
expect(cdRef.markForCheck).toHaveBeenCalled(); // fail, why ??
expect(cmp.cdRef.markForCheck).toHaveBeenCalled(); // success
console.log(cdRef); // logs ChangeDetectorRefMock
console.log(cmp.cdRef); // logs ChangeDetectorRef , why ??
}));
#Component({
...
})
export class MyComponent {
constructor(private cdRef: ChangeDetectorRef){}
ngOnInit() {
// do something
this.cdRef.markForCheck();
}
}
I have tried everything , async, fakeAsync, injector([ChangeDetectorRef], () => {}).
Nothing works.
Update 2020:
I wrote this originally in May 2017, it's a solution that worked great at the time and still works.
We can't configure the injection of a changeDetectorRef mock through the test bed, so this is what I am doing these days:
it('detects changes', () => {
// This is a unique instance here, brand new
const changeDetectorRef = fixture.debugElement.injector.get(ChangeDetectorRef);
// So, I am spying directly on the prototype.
const detectChangesSpy = spyOn(changeDetectorRef.constructor.prototype, 'detectChanges');
component.someMethod(); // Which internally calls the detectChanges.
expect(detectChangesSpy).toHaveBeenCalled();
});
Then you don't care about private attributes or any.
In case anyone runs into this, this is one way that has worked well for me:
As you are injecting the ChangeDetectorRef instance in your constructor:
constructor(private cdRef: ChangeDetectorRef) { }
You have that cdRef as one of the private attributes on the component, which means you can spy on the component, stub that attribute and have it return whatever you want. Also, you can assert its calls and parameters, as needed.
In your spec file, call your TestBed without providing the ChangeDetectorRef as it won't provide what you give it. Set the component that same beforeEach block, so it is reset between specs as it is done in the docs here:
component = fixture.componentInstance;
Then in the tests, spy directly on the attribute
describe('someMethod()', () => {
it('calls detect changes', () => {
const spy = spyOn((component as any).cdRef, 'detectChanges');
component.someMethod();
expect(spy).toHaveBeenCalled();
});
});
With the spy you can use .and.returnValue() and have it return whatever you need.
Notice that (component as any) is used as cdRef is a private attribute. But private doesn't exist in the actual compiled javascript so it is accessible.
It is up to you if you want to access private attributes at runtime that way for your tests.
Not sure if this a new thing or not, but changeDetectorRef can be accessed via fixture.
See docs: https://angular.io/guide/testing#componentfixture-properties
We ran into the same issue with change detector mocking and this is ended up being the solution
Probably one point that needs to be pointed out, is that in essence here you want to test your own code, not unit test the change detector itself (which was tested by the Angular team).
In my opinion this is a good indicator that you should extract the call to the change detector to a local private method (private as it is something you don't want to unit test), e.g.
private detectChanges(): void {
this.cdRef.detectChanges();
}
Then, in your unit test, you will want to verify that your code actually called this function, and thus called the method from the ChangeDetectorRef. For example:
it('should call the change detector',
() => {
const spyCDR = spyOn((cmp as any).cdRef, 'detectChanges' as any);
cmp.ngOnInit();
expect(spyCDR).toHaveBeenCalled();
}
);
I had the exact same situation, and this was suggested to me as a general best practice for unit testing from a senior dev who told me that unit testing is actually forcing you by this pattern to structure your code better. With the proposed restructuring, you make sure your code is flexible to change, e.g. if Angular changes the way they provide us with change detection, then you will only have to adapt the detectChanges method.
For unit testing, if you are mocking ChangeDetectorRef just to satisfy dependency injection for a component to be creation, you can pass in any value.
For my case, I did this:
TestBed.configureTestingModule({
providers: [
FormBuilder,
MyComponent,
{ provide: ChangeDetectorRef, useValue: {} }
]
}).compileComponents()
injector = getTestBed()
myComponent = injector.get(MyComponent)
It will create myComponent successfully. Just make sure test execution path does not need ChangeDetectorRef. If you do, then replace useValue: {} with a proper mock object.
In my case, I just needed to test some form creation stuff using FormBuilder.
// component
constructor(private changeDetectorRef: ChangeDetectorRef) {}
public someHandler() {
this.changeDetectorRef.detectChanges();
}
// spec
const changeDetectorRef = fixture.componentRef.changeDetectorRef;
jest.spyOn(changeDetectorRef, 'detectChanges');
fixture.detectChanges(); // <--- needed!!
component.someHandler();
expect(changeDetectorRef.detectChanges).toHaveBeenCalled();

How to add service to Angular2 test (CLI config, not as in docs)

I'm just now learning unit testing in general, but I'm simultaneously learning angular2. Angular CLI created my test scaffolds and I've configured the testing module with the neccessary component and directive. I'm trying to provide a stub service similar to the one decribed in the DOCS, but I'm getting the following error:
Error: Cannot create the component FoodListComponent as it was not imported
into the testing module
This is odd because the should create test passed when with the FoodListComponent before I added the service into the providers: []. My service works fine in the application, but I can provide it too if you need to see it.
Basically I'd like to know where in this testing set up I need to put the foodDataServiceStub, whether I'm adding the stub service to the TestBed object correctly, and where I need to inject the service via foodDataService = TestBed.get(FoodDataService);?
I'd be thankful for an explanation as well.
food-list.component.spec.ts
/* tslint:disable:no-unused-variable */
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { By } from '#angular/platform-browser';
import { DebugElement, NO_ERRORS_SCHEMA } from '#angular/core';
import { TickerDirective } from '../../directives/ticker.directive';
import { FoodListComponent } from './food-list.component';
describe('FoodListComponent', () => {
let component: FoodListComponent;
let fixture: ComponentFixture<FoodListComponent>;
let foodDataServiceStub = {
name: 'test food name',
img: '',
description: 'test food description'
};
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FoodListComponent, TickerDirective ],
providers: [ { provide: FoodDataService, useValue: foodDataServiceStub } ], /* This line gives me the error */
schemas: [ NO_ERRORS_SCHEMA ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FoodListComponent);
component = fixture.componentInstance;
// foodDataService = TestBed.get(FoodDataService);
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

Angular 2 - When to use the inject function and when to not use it

I have a question in creating specs/unit tests in Angular2. Whenever you are injecting a mocked service, when do you use the inject function as the one below
it('function that the component calls',
inject([MyService], (service: MyService) => { // ...
}));
Or when do you use it as the one below
beforeEach(() => {
let myMockService = new MyMockService();
TestBed.configureTestingModule({
providers: [
{ provide: MyService, useValue: myMockService }
]
});
TestBed.overrideComponent(MyComponent, {
set: {
providers: [
{ provide: MyService, useValue: myMockService }
]
}
});
})
Can someone enlighten me on the matter?
The main reason to use it is to get access to the service in your test, when Angular is creating it. For instance
providers: [ MyService ]
Here Angular is creating it, and you only have access to it through Angular's injector.
But you're providing the service as a value then there is no need to use inject as you already have access to it
let serviceInstance = new Service();
provider: [ { provide: MyService, useValue: serviceInstance } ]
Here you already have access to serviceInstance so no need to get it from the injector.
Also if you don't need to access to the service inside the test, then there's not even a need to try and access it. But sometimes your mock will have thing you want to do with it inside the test.
Aside from inject, there are only ways to access the service
You could...
For your particular example you don't need inject at all. You just need to move the mock outside the scope of the beforeEach so that the it can use it
let myMockService = new MyMockService();
beforeEach(() => {
})
it('function that the component calls', () => {
myMockService.doSomething();
}));
You could...
Instead of using inject, you could get it from the TestBed, which acts like an injector. Maybe this is preferred as you can add it in your beforeEach
let service;
beforeEach(() => {
TestBed.configureTestingModule({
providers: [ MyService ]
});
service = TestBed.get(MyService);
})
it('function that the component calls', () => {
service.doSomething();
}));
You could...
Get it from the DebugElement which also acts like an injector
it('function that the component calls', () => {
let fixture = TestBed.createComponent(TestComponent);
let service = fixture.debugElement.get(MyService);
}));
So it's really a matter of preference. I personally try to stop using inject, as there are the other, less verbose options.
You use inject() when you want to get instances from the provide passed into your test code (for example the HTTP MockBackend) or any other service you want to communicate to directly in your test code.
TestBed.configureXxx only sets up providers for the test component.

angular2 rc.4: How to inject mocked services (provide() is deprecated)

Since angular 2.0.0-rc.4 release (change log), both beforeEachProviders and provide are deprecated. My question is how can I inject my mocked service inside my component.
Here is my beforeEach function which creates a ComponentFixture with overrided providers:
beforeEach(async(inject([TestComponentBuilder], (tcb)=> {
builder = tcb;
})));
beforeEach(async(inject([], ()=> {
return builder
.overrideProviders(MenubarComponent,[provide(MenubarService, {useClass:MenubarServiceMock})])
.createAsync(MenubarTestComponent)
.then((_fixture:ComponentFixture<any>)=> {
fixture = _fixture;
})
})));
This approach works fine but as I said provide is deprecated.
What is the right way to do this without using provide()?
Edit
Based on Günter's answer I changed the line:
.overrideProviders(MenubarComponent,[provide(MenubarService, {useClass:MenubarServiceMock})])
to:
.overrideProviders(MenubarComponent,[{provide:MenubarService, useClass:MenubarServiceMock}])
and it works!
From the changelog
import {addProviders, inject} from '#angular/core/testing';
describe('my code', () => {
beforeEach(() => {
addProviders([MyService]);
});
it('does stuff', inject([MyService], (service) => {
// actual test
});
});
Instead of provide() use
{provide: MenubarService, useClass:MenubarServiceMock}

Angular2 Testing Component with Provider dependency

I have a simple angular2 component as defined below. And I'm looking to create a unit testing with karma, jasmine to run through this component.
#Component({
selector: 'property',
template: require('./property.component.html'),
directives: [Panel],
providers: [ConfigService]});
export class PropertyComponent {
config:any;
constructor(config:ConfigService) {
this.config = config.getConfig();
}
}
This is my testing spec file.
describe('property component', () => {
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
let propertyComp = fixture.componentInstance,
element = fixture.nativeElement;
expect(element.querySelector('h1').innerText).toBe('property page');
});
}));
})
However I got a list of weird errors... I'm guessing this is due to the ConfigService Provider in the PropertyComponent, because when I removed the provider dependency, it went through.
Does anyone know how to deal with the dependency Providers?
Thanks!
errors:
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
getObjByKeyId#angular2-seed/config/spec-bundle.js:22937:38
_getByKeyDefault#angular2-seed/config/spec-bundle.js:23641:51
_getByKey#angular2-seed/config/spec-bundle.js:23587:42
_getByDependency#angular2-seed/config/spec-bundle.js:23573:35
_instantiate#angular2-seed/config/spec-bundle.js:23463:53
_instantiateProvider#angular2-seed/config/spec-bundle.js:23435:38
_new#angular2-seed/config/spec-bundle.js:23424:42
instantiateProvider#angular2-seed/config/spec-bundle.js:22924:35
init#angular2-seed/config/spec-bundle.js:34694:44
AppElement#angular2-seed/config/spec-bundle.js:34371:33
viewFactory_HostPropertyComponent0
createRootHostView#angular2-seed/config/spec-bundle.js:35741:48
You need to use beforeEachProviders in this case:
import {beforeEachProviders, describe, it, expect} from 'angular2/testing';
//...other imports...
describe('property component', () => {
beforeEachProviders(()=> [
ConfigService, //if you don't need to mock
provide(ConfigService, {useClass:MockConfigService}) // more typical
]);
it('should have property page title', injectAsync([TestComponentBuilder], (tcb) => {
return tcb.createAsync(PropertyComponent).then((fixture) => {
//expectations...
});
}));
})
Note that you need to import angular's patched describe, it, expect functions along with beforeEachProvidersfrom angular2/testing. I emphasize this because it's easy to forget to do that, and it results in failures with rather unintuitive messages.