Search code examples
angularjestjskarma-jasmineangular-spectatorng-mocks

Mock Angular Router with Spectator


I am using Spectator to write my Angular 8 tests and Jest to run them. I am new to frontend unit testing, so I may have overlooked something simple; any ideas are welcome.

I have the following method (in Typescript) that returns a boolean, based on whether the current URL matches a set of paths or not (excluding queryParams and fragments):

// custom-breadcrumb.component.ts

private blacklistedPaths: string[] = [''];

constructor(private router: Router) {
}

hideBreadcrumb(): boolean {
    let primaryUrlSegmentGroup: UrlSegmentGroup = this.router.parseUrl(this.router.url).root.children['primary'];
    if(primaryUrlSegmentGroup == null){
        return true;
    }
    let urlPath = primaryUrlSegmentGroup.segments.map((segment: UrlSegment) => segment.path).join('/');
    return this.blacklistedPaths.some((path: string) => path === urlPath);
}

and

// custom-breadcrumb.component.html

<xng-breadcrumb [hidden]="hideBreadcrumb()">
    <ng-container *xngBreadcrumbItem="let breadcrumb">
        ...
    </ng-container>
</xng-breadcrumb>

I now want to write tests with Spectator that will validate the boolean return value based on a couple of possible urls. In Java I would emulate the Router with a mock object and perform something along the lines of:

when(mockObject.performMethod()).thenReturn(myReturnValue);

How do I create a mock for the Router? And how do I define the return values for this.router.parseUrl(this.router.url).root.children['primary']?

This is what I currently have:

// custom-breadcrumb.component.spec.ts

import {SpectatorRouting, createRoutingFactory} from '@ngneat/spectator/jest';

describe('CustomBreadcrumbComponent', () => {
    let spectator: SpectatorRouting<CustomBreadcrumbComponent>;
    const createComponent = createRoutingFactory({
        component: CustomBreadcrumbComponent,
        declarations: [
            MockComponent(BreadcrumbComponent),
            MockPipe(CapitalizePipe)
        ],
        routes: [{path: ''}]  // I don't think this works
    });

    beforeEach(() => spectator = createComponent());

    it('hideBreadcrumb - hide on homepage', () => {
        // TODO set url path to ''
        expect(spectator.component.hideBreadcrumb()).toBeTruthy();
    });

    it('hideBreadcrumb - show on a page other than homepage', () => {
        //TODO set url path to 'test' for example
        expect(spectator.component.hideBreadcrumb()).toBeFalsy();
    });
});

I know that createRoutingFactory supplies a ActivatedRouteStub out of the box, but I haven't been able to do anything meaningful with it.

PS: I added karma as a tag as it may have the same solution, but do correct me if I am wrong.


Solution

  • I was under the impression that spectator.router was returning me a mock, while I had to use spectator.get<Router>(Router) to get it. The other problem I had was that the hideBreadcrumb method in the html template was being loaded at component creation while I did not have the chance yet to mock the Router. This is how I solved it:

    Set detectChanges to false to prevent the html template and ngOnInit from being loaded when I create the spectator component like so:

    let spectator: SpectatorRouting<CustomBreadcrumbComponent>;
    const createComponent = createRoutingFactory({
        detectChanges: false,
        component: CustomBreadcrumbComponent,
        declarations: [ ... ]
    });
    
    beforeEach(() => {
        spectator = createComponent()
    });
    

    Now it won't call hideBreadcrumb() yet, which allows for successful creation of the spectator.

    My test is this:

    it('hideBreadcrumb - hide on homepage', () => {
        let routerMock = spectator.get<Router>(Router);
        routerMock.parseUrl.andReturn({root: {children: {'primary': {segments: [{path: ''}]}}}});
        spectator.detectChanges();
    
        expect(spectator.component.hideBreadcrumb()).toBeTruthy();
    });
    

    I retrieve a mock from the spectator with spectator.get<Router>(Router) and I mock the return value of the parseUrl method. I now allow the html template and ngOnInit progression by setting spectator.detectChanges().