Testing a component, which depends on a route param
Asked Answered
E

1

8

I have a question about testing a routed component in angular2.

Here is a simple component, which depends on a route with a parameter 'foo'. The attribute foo in the component will be set to the value of the parameter.

import {Component, OnInit} from '@angular/core';
import {ActivatedRoute, Params} from '@angular/router';

@Component({
    selector: 'my-component',
    templateUrl: './my-component.html'
})
export class MyComponent implements OnInit
{
    foo: string;

    constructor(
        private route: ActivatedRoute
    )
    {
    }

    ngOnInit()
    {
        this.route.params.subscribe((params: Params) => {
            this.foo = params['foo'];
        });
    }
}

Now I want to test, that if the component will be created with the route, the param will be set correctly. So somewhere I want to have expect(component.foo).toBe('3');.

import {TestBed, ComponentFixture, async} from '@angular/core/testing';
import {DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {Params, ActivatedRoute} from '@angular/router';

import {Observable} from 'rxjs';

import {MyComponent} from './MyComponent';

describe('MyComponent', () => {
    let mockParams, mockActivatedRoute;

    let component: MyComponent;
    let fixture: ComponentFixture<MyComponent>;
    let debugElement: DebugElement;
    let element: HTMLElement;

    beforeEach(async(() => {
        mockParams = Observable.of<Params>({foo: '3'});
        mockActivatedRoute = {params: mockParams};

        TestBed.configureTestingModule({
            declarations: [
                MyComponent
            ],
            providers: [
                {provide: ActivatedRoute, useValue: mockActivatedRoute}
            ]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(MyComponent);
        component = fixture.componentInstance;

        debugElement = fixture.debugElement;
        element = debugElement.nativeElement;

        fixture.detectChanges();
    });

    it('should set foo to "3"', () => {
        expect(component.foo).toBe('3');
    });
});

My problem is that I don't know how to wait, until resolving of the route is finished, and I can do an expect(). And in this example the test fails and it says "Expected undefined to be '3'.".

Can someone help me please?!

Thank you!

Elroy answered 22/2, 2017 at 8:6 Comment(1)
Does this answer your question? Angular 2 - test for change in route paramsAssessment
E
9

Okay, reading a little bit in the angular2 testing documentation, I saw their ActivatedRouteStub class. I have created this stub class, and have replaced my original mock with this new class. Now it is working (recognize the line mockActivatedRoute.testParams = {foo: '3'}; in the second beforeEach).

import {TestBed, ComponentFixture, async} from '@angular/core/testing';
import {DebugElement} from '@angular/core';
import {By} from '@angular/platform-browser';
import {Params, ActivatedRoute} from '@angular/router';

import {Observable} from 'rxjs';

import {MyComponent} from './MyComponent';

import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class ActivatedRouteStub
{
    private subject = new BehaviorSubject(this.testParams);
    params = this.subject.asObservable();

    private _testParams: {};
    get testParams() { return this._testParams; }
    set testParams(params: {}) {
        this._testParams = params;
        this.subject.next(params);
    }
}

describe('MyComponent', () => {
    let mockParams, mockActivatedRoute;

    let component: MyComponent;
    let fixture: ComponentFixture<MyComponent>;
    let debugElement: DebugElement;
    let element: HTMLElement;

    beforeEach(async(() => {
        mockActivatedRoute = new ActivatedRouteStub();

        TestBed.configureTestingModule({
            declarations: [
                MyComponent
            ],
            providers: [
                {provide: ActivatedRoute, useValue: mockActivatedRoute}
            ]
        }).compileComponents();
    }));

    beforeEach(() => {
        fixture = TestBed.createComponent(MyComponent);
        component = fixture.componentInstance;

        debugElement = fixture.debugElement;
        element = debugElement.nativeElement;

        mockActivatedRoute.testParams = {foo: '3'};

        fixture.detectChanges();
    });

    it('should set foo to "3"', () => {
        expect(component.foo).toBe('3');
    });
});

Do you think this is the right way?

Elroy answered 22/2, 2017 at 8:20 Comment(4)
Your test, which validates the component data gets set according to the route param, looks good to me. The test is relying only on the mock implementation of ActivatedRoute, not the real one. That's good. Unit tests should be as isolated as possible. The purpose of a test like this is to identify issues in just the one component itself, not interactions with other components or services or Angular classes or the browser. If you introduced other such unnecessary interactions, it would cloud the test results, because those other things could mask issues within your component.Slopwork
Thank you so much for taking the time to answer your own question to help those of us who are stuck with the same problem. I've tried a few different ways of mocking out the route params now but this is the only solution I've found that allows the params to be changed on a per test basis.Delrio
@be-ndee Why do I get this error TypeError: Cannot read properties of undefined (reading 'subscribe') at this line of code this.activeRoute.paramMap.subscribe(params => {....});Ulcerate
Can you post more of your component code? Important are the imports, constructor and the function where your line of code is in.Elroy

© 2022 - 2024 — McMap. All rights reserved.