Angular 路由器、面包屑组件测试

Angular router, breadcrumbs component testing

我有 面包屑组件,测试如下。测试应覆盖75%以上。测试失败,因为 ActivatedRoute.root.children 未定义。谁知道我该如何定义它?我需要至少一个 children for root

Breadcrumbs.component:

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

import { Breadcrumb } from './breadcrumbs.interface.component';
import * as _ from 'lodash';
import 'rxjs/add/operator/filter';

@Component({
 selector: 'breadcrumbs',
 styleUrls: ['./breadcrumbs.component.scss'],
 templateUrl: './breadcrumbs.component.html'
})

export class BreadcrumbsComponent implements OnInit {
private breadcrumbs: Breadcrumb[];

constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    ) {
}
public ngOnInit() {
    this.breadcrumbs = [];
    this.router.events.filter((event) => event instanceof NavigationEnd)
    .subscribe((event: NavigationEnd) => {
        let id = event.url;
        let root: ActivatedRoute = this.activatedRoute.root;
        this.breadcrumbs = this.getBreadcrumbs(root, '/main', this.breadcrumbs, id);
    });
}

public goTo = (breadcrumb) => this.router.navigateByUrl(breadcrumb.path);

private getBreadcrumbs(
    route: ActivatedRoute,
    url: string = '',
    breadcrumbs: Breadcrumb[] = [],
    id: string
    ): Breadcrumb[] {
    const ROUTE_DATA_BREADCRUMB: string = 'title';
    // get the child routes
    let children: ActivatedRoute[] = route.children;

    // return if there are no more children
    if (children.length === 0) {
        return breadcrumbs;
    }

    // iterate over each children
    for (let child of children) {
        // verify primary route
        if (child.outlet !== PRIMARY_OUTLET) {
            continue;
        }

        // verify the custom data property "breadcrumb" is specified on the route
        if (!child.snapshot.data.hasOwnProperty(ROUTE_DATA_BREADCRUMB)) {
            return this.getBreadcrumbs(child, url, breadcrumbs, id);
        }
        // get the route's URL segment
        let routeURL: string = child.snapshot.url.map((segment) => segment.path).join('/');

        // append route URL to URL
        url += `/${routeURL}`;
        let title = child.snapshot.data[ROUTE_DATA_BREADCRUMB]
        || child.snapshot.params[ROUTE_DATA_BREADCRUMB];
        let isNew = child.snapshot.data['new'];
        // add breadcrumb
        let breadcrumb: Breadcrumb = {
            title,
            params: child.snapshot.params,
            url,
            id
        };
        if (isNew) {
            breadcrumbs = [breadcrumb];
        } else {
            let index = _.findLastIndex(breadcrumbs, { id });
            if (index > -1) {
                breadcrumbs.splice(index + 1);
            } else {
                breadcrumbs.push(breadcrumb);
            }
        }

        // recursive
        return this.getBreadcrumbs(child, url, breadcrumbs, id);
    }

    // we should never get here, but just in case
    return breadcrumbs;
}
}

Breadcrumbs.component.spec:

import {
 TestBed,
 ComponentFixture,
 async,
 inject
} from '@angular/core/testing';
import { Component } from '@angular/core';
import { Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { CommonModule } from '@angular/common';

import { BreadcrumbsComponent } from './breadcrumbs.component';
import { RouterLinkStubDirective, ActivatedRouteMock, MockComponent } from '../../testing';
import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

class RouterStub {
public navigate = jasmine.createSpy('navigate');
public ne = new NavigationEnd(0,
    '/main/sps',
    '/main/sps');
public events = new Observable((observer) => {
    observer.next(this.ne);
    observer.complete();
});
public navigateByUrl(url: string) { location.hash = url; }
}

let ROUTES = [
{
    path: 'main',
    component: MockComponent,
    children: [
        {
            path: 'link1',
            component: MockComponent
        },
        {
            path: 'link2',
            component: MockComponent
        },
    ]
},
{
    path: '',
    component: MockComponent,
},
];

describe('Component: BreadcrumbsComponent', () => {
let comp: BreadcrumbsComponent;
let fixture: ComponentFixture<BreadcrumbsComponent>;
let routerStub = new RouterStub();
let mockActivatedRoute = new ActivatedRouteMock();
let router: Router;
beforeEach(async(() => {
    TestBed.configureTestingModule({
        declarations: [
            BreadcrumbsComponent,
            RouterLinkStubDirective,
            MockComponent
        ],
        providers: [
            { provide: Router, useValue: routerStub },
            { provide: ActivatedRoute, useValue: mockActivatedRoute },
        ],
        imports: [
            NoopAnimationsModule,
            CommonModule,
            RouterTestingModule.withRoutes(ROUTES)
        ]
    })
        .compileComponents()
        .then(() => {
            fixture = TestBed.createComponent(BreadcrumbsComponent);
            comp = fixture.componentInstance;
            fixture.detectChanges();
        });
}));
it('should be init', () => {
    expect(comp).toBeTruthy();
});
});

部分日志:

Component: BreadcrumbsComponent ✖ should be init Chrome 58.0.3029 (Mac OS X 10.12.3) Component: BreadcrumbsComponent should be init FAILED TypeError: Cannot read property 'children' of undefined at BreadcrumbsComponent.getBreadcrumbs (webpack:///src/app/components/breadcrumbs/breadcrumbs.component.ts:33:29 <- config/spec-bundle.js:122693:43) at SafeSubscriber._next (webpack:///src/app/components/breadcrumbs/breadcrumbs.component.ts:25:38 <- config/spec-bundle.js:122692:1206)

不确定你是如何模拟 ActivatedRoute 的,但我们是这样使用它的:

{ provide: ActivatedRoute, useValue: { params: Observable.of({ productcode: product.id }) }},

所以你也可以在那里设置你的根和children,类似于

{ provide: ActivatedRoute, useValue: { root: { children: ['something'] } }},