在 Angular 路由定义中发布测试数据

Issues testing data in an Angular route definition

我正在尝试测试一个基于路由中的数据动态设置标题的组件,但我在测试位上遇到了问题。

我正在尝试模拟路由数据,但在我获取数据的方法中 findRouteData 它在调试测试时最终未定义。

我可以直观地验证组件本身是否按照我的要求执行,但是我在模拟路由数据位时遇到了问题。

就像我说的,一切都在被调用,但是我的 routerState 模拟没有正常工作。数据未定义。

如何正确模拟路由数据?

@Injectable()
class FakeRouter {
    url: string;
    subject = new Subject();
    events = this.subject.asObservable();
    routerState = {
        snapshot: {
            root: {
                data: {title: 'Title'}
            }
        }
    };
    navigate(url: Array<string>): void {
        this.url = url.join('');
        this.triggerNavEvents(this.url);
    }

    triggerNavEvents(url: string): void {
        this.subject.next(new NavigationEnd(0, url, null));
    }
}

组件本身:

import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, OnDestroy } from '@angular/core';
import { ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
import { Subject } from 'rxjs/Subject';

@Component({
    selector: 'hq-page-title',
    templateUrl: './page-title.component.html',
    styleUrls: ['./page-title.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush
})
export class PageTitleComponent implements OnInit, OnDestroy {
    public title;
    private unsubscribe = new Subject<boolean>();

    constructor(private router: Router, private cdRef: ChangeDetectorRef) {
    }

    ngOnInit(): void {
        this.router.events
            .filter((event: any) => event instanceof NavigationEnd)
            .takeUntil(this.unsubscribe)
            .subscribe(() => {
                const test = this.router;
                const test1 = this.router.routerState;
                const routeData = this.findRouteData(this.router.routerState.snapshot.root);
                if (routeData.hasOwnProperty('title')) {
                    this.title = routeData.title;
                    this.cdRef.detectChanges();
                }
            });
    }

    ngOnDestroy(): void {
        this.unsubscribe.next();
        this.unsubscribe.complete();
    }

    /**
     * Returns the route data
     *
     * Check out the following links to see why it was implemented this way:
     * @link https://github.com/angular/angular/issues/19420
     * @link https://github.com/angular/angular/issues/11812#issuecomment-346637722
     *
     * @param {ActivatedRouteSnapshot} root
     * @returns {any}
     */
    private findRouteData(root: ActivatedRouteSnapshot) {
        let data = <any>{};
        while (root) {
            if (root.children && root.children.length) {
                root = root.children[0];
            } else if (root.data) {
                data = {...data, ...root.data};
                return data;
            } else {
                return data;
            }
        }
    }

}

示例路线:

{
    path: 'settings',
    data: {title: 'Settings'},
} 

测试:

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { PageTitleComponent } from './page-title.component';
import {NavigationEnd, Router} from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import {Subject} from 'rxjs/Subject';
import {Injectable} from '@angular/core';

@Injectable()
class FakeRouter {
    url: string;
    subject = new Subject();
    events = this.subject.asObservable();
    routerState = {
        snapshot: {
            root: {
                data: {title: 'Title'}
            }
        }
    };
    navigate(url: Array<string>): void {
        this.url = url.join('');
        this.triggerNavEvents(this.url);
    }

    triggerNavEvents(url: string): void {
        this.subject.next(new NavigationEnd(0, url, null));
    }
}

describe('PageTitleComponent', () => {
    let component: PageTitleComponent;
    let fixture: ComponentFixture<PageTitleComponent>;
    let router: Router;

    beforeEach(async(() => {
        TestBed.configureTestingModule({
            imports: [RouterTestingModule],
            declarations: [
                PageTitleComponent
            ],
            providers: [
                { provide: router, useClass: FakeRouter }
            ]
        })
            .compileComponents()
            .then(() => {
                fixture = TestBed.createComponent(PageTitleComponent);
                component = fixture.componentInstance;

                router = TestBed.get(Router);
                fixture.detectChanges();
            });
    }));
    it('should create', () => {
        expect(component).toBeTruthy();
    });

    describe('the title should be title', () => {
        beforeEach(async(() => {
            router.navigate(['']);
        }));
        it('the title should be title', () => {
            expect(component.title).toBe('Title');
        });
    });
});

我解决了我自己的问题,因为这些事情经常发生。

我没有尝试进入 router.routerState 获取路线数据,而是改用 ActivatedRoute,并使用了其中的快照。我之前回避了该实现,因为我发现订阅 ActivatedRoutedata 属性 不会产生路由 data。它最终只是一个空白对象。

但是,我发现如果我查看 ActivatedRoute 一旦路线事件达到 NavigationEnd,我要查找的所有数据都在那里。

ActivatedRoute 更容易模拟。在 TestBed.configureTestingModule() 内,我将此片段添加到提供者:

{
    provide: ActivatedRoute,
    useValue: {
        snapshot: {
            data: {title: 'Title'}
        }
    },
},

我的主要 TS 文件中的更改如下:

constructor(private router: Router, private activatedRoute: ActivatedRoute, private cdRef: ChangeDetectorRef) {
}

ngOnInit(): void {
    this.router.events
        .filter((event: any) => event instanceof NavigationEnd)
        .takeUntil(this.unsubscribe)
        .subscribe(() => {
            const routeData = this.findRouteData(this.activatedRoute.snapshot);
            if (routeData.hasOwnProperty('title')) {
                this.title = routeData.title;
                this.cdRef.detectChanges();
            }
        });
}

我从模拟路由器中删除了这个位:

routerState = {
    snapshot: {
        root: {
            data: {title: 'Title'}
        }
    }
};

真的没有太多变化。

注意:我可能仍然遗漏了一些东西。如果您认为我是,请告诉我。