出于测试目的,如何在 angular2 中模拟已激活的父路由?
Posted
技术标签:
【中文标题】出于测试目的,如何在 angular2 中模拟已激活的父路由?【英文标题】:How to mock an activatedRoute parent Route in angular2 for testing purposes? 【发布时间】:2017-04-08 22:45:57 【问题描述】:假设我有这个
export class QuestionnaireQuestionsComponent
questions: Question[] = [];
private loading:boolean = true;
constructor(
private route: ActivatedRoute,
public questionnaireService:QuestionnaireService)
ngOnInit()
this.route.parent.params.subscribe((params:any)=>
this.questionnaireService.getQuestionsForQuestionnaire(params.id).subscribe((questions)=>
this.questions = questions;
this.loading = false;
);
);
我的组件实际上运行良好。问题是我想对其进行单元测试,但我不知道如何模拟 this.route.parent
对象。这是我失败的测试
beforeEach(()=>
route = new ActivatedRoute();
route.parent.params = Observable.of(id:"testId");
questionnaireService = jasmine.createSpyObj('QuestionnaireService', ['getQuestionsForQuestionnaire']);
questionnaireService.getQuestionsForQuestionnaire.and.callFake(() => Observable.of(undefined));
component = new QuestionnaireQuestionsComponent(route, questionnaireService);
);
describe("on init", ()=>
it("must call the service get questions for questionnaire",()=>
component.ngOnInit();
expect(questionnaireService.getQuestionsForQuestionnaire).toHaveBeenCalled();
);
);
测试失败并出现此错误
TypeError: undefined is not an object (evaluating 'this._routerState.parent')
【问题讨论】:
尝试使用 parent: params: Observable.of(id:"testId") as ActivatedRoute
而不是真正的ActivatedRoute
不会编译我需要一个 ActivatedRoute 对象
你添加了as ActivatedRoute
吗?
你没有在你的类定义中添加implements OnInit
【参考方案1】:
AcitvatedRoute是根据angular2docs的接口,所以我做的是实现一个MockActivatedRoute
import Observable from 'rxjs';
import Type from '@angular/core';
import ActivatedRoute,Route,ActivatedRouteSnapshot,UrlSegment,Params,Data from '@angular/router';
export class MockActivatedRoute implements ActivatedRoute
snapshot : ActivatedRouteSnapshot;
url : Observable<UrlSegment[]>;
params : Observable<Params>;
queryParams : Observable<Params>;
fragment : Observable<string>;
data : Observable<Data>;
outlet : string;
component : Type<any>|string;
routeConfig : Route;
root : ActivatedRoute;
parent : ActivatedRoute;
firstChild : ActivatedRoute;
children : ActivatedRoute[];
pathFromRoot : ActivatedRoute[];
toString() : string
return "";
;
只需在我的测试中将ActivatedRoute
替换为MockActivatedRoute
,就像这样
beforeEach(()=>
route = new MockActivatedRoute();
route.parent = new MockActivatedRoute();
route.parent.params = Observable.of(id:"testId");
questionnaireService = jasmine.createSpyObj('QuestionnaireService', ['getQuestionsForQuestionnaire']);
questionnaireService.getQuestionsForQuestionnaire.and.callFake(() => Observable.of(undefined));
component = new QuestionnaireQuestionsComponent(route, questionnaireService);
);
【讨论】:
感谢您,即使对于您不关心父母的已激活路线,它也非常有帮助。问题:您是否有理由更喜欢更新组件而不是注入路由和服务? 只是因为我没有测试组件的行为......所以真的不需要注入它,组件不应该改变我的测试用例中的任何东西 也可以参考文档:angular.io/docs/ts/latest/testing#!#create-an-observable-test-double 这两个测试文档链接都已损坏:( 我无法分配'父',因为它是常量或只读属性。还有缺少的道具和接口:paramMap 和 queryParamMap。应该为他们准备什么,这将在 Anuglar 4 中起作用吗?【参考方案2】:使用测试床
beforeEach(async(() =>
TestBed.configureTestingModule(
declarations: [YourComponent],
imports: [],
providers: [
provide: ActivatedRoute, useValue:
params: Observable.of( id: 'test' )
]
)
.compileComponents();
));
【讨论】:
TestBed有时候编译的时候很慢,不需要的时候不用加载TestBed。 如果我想抓住这个怎么办:this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/someurl'; ? 如何更改我的 it(...) 中的 id 值?【参考方案3】:你也可以简单的打params: searchTerm: 'this is not me' as any) as ActivatedRouteSnapshot
详细代码
(service.resolve((params: id: 'this is id' as any) as ActivatedRouteSnapshot,
as RouterStateSnapshot)as Observable<xyzResolveData>)
.subscribe((data) =>
expect((data as xyzResolveData).results).toEqual(xyzData.results);
);
【讨论】:
【参考方案4】:对于这个问题的新人,angular docs 涵盖了这个场景。
根据上述文档:
创建ActivatedRouteStub
类以用作ActivatedRoute
的测试替身
import ReplaySubject from 'rxjs/ReplaySubject';
import convertToParamMap, ParamMap, Params from '@angular/router';
/**
* An ActivateRoute test double with a `paramMap` observable.
* Use the `setParamMap()` method to add the next `paramMap` value.
*/
export class ActivatedRouteStub
// Use a ReplaySubject to share previous values with subscribers
// and pump new values into the `paramMap` observable
private subject = new ReplaySubject<ParamMap>();
constructor(initialParams?: Params)
this.setParamMap(initialParams);
/** The mock paramMap observable */
readonly paramMap = this.subject.asObservable();
/** Set the paramMap observables's next value */
setParamMap(params?: Params)
this.subject.next(convertToParamMap(params));
;
然后在测试类中使用stub如下:
activatedRoute.setParamMap( id: '1234' );
【讨论】:
感谢您的评论。我已经用文档中的代码示例更新了答案。【参考方案5】:要在每个“it”块中自定义模拟的 ActivatedRoute 数据,请结合上面 Kevin Black 的建议
beforeEach(async(() =>
TestBed.configureTestingModule(
declarations: [YourComponent],
imports: [],
providers: [
provide: ActivatedRoute, useValue:
queryParams: of( id: 'test' )
]
)
.compileComponents();
));
在使用 fixture.detectChanges() 实例化组件之前,it('') 块中的以下代码
it('test', fakeAsync(() =>
let activatedRoute: ActivatedRoute = fixture.debugElement.injector.get(ActivatedRoute);
activatedRoute.queryParams = of( firstName: 'john', lastName: 'smith' );
fixture.detectChanges(); // trigger ngOninit()
tick();
));
【讨论】:
在 complieComponents() 调用后如何覆盖提供者值的好例子【参考方案6】:对于您从 ActivatedRouteSnapshot 中读取查询参数的场景,例如:
this.someProperty = this.route.snapshot.data.query['paramKey'];
下面的代码将有助于将查询参数数据输入路由的测试
provide: ActivatedRoute,
useValue:
snapshot:
data:
query:
paramKey: 'paramValue'
【讨论】:
正确解释。这一个值得正确答案 这种方法的实际测试如何?如何访问expect里面的值?【参考方案7】:基于@S.Galarneau 接受的答案,这里是更新后的 ActivatedRoute 接口(角度 4+),带有访问修饰符,仅使用少数类型,以及使用 providers 数组并与 TestBed 集成:
这非常适合创建模拟接口并使用 providers 数组来使用您的 ActivatedRoute 版本:
export class MockActivatedRoute implements ActivatedRoute
public snapshot;
public url;
public params;
public queryParams: Observable<Params>;
public fragment: Observable<string>;
public data;
public outlet;
public component;
public paramMap;
public queryParamMap;
public routeConfig;
public root;
public parent;
public firstChild;
public children;
public pathFromRoot;
public toString(): string
return '';
;
并与TestBed和Providers数组一起使用,只处理片段属性,像这样:
beforeEach(async((): void =>
const route = new MockActivatedRoute();
route.fragment = of('#myhash?some_params=some_value');
// because fragment is an Observable, we need `of(value)`
TestBed.configureTestingModule(
// declarations, imports, and providers go next:
providers:[
provide: ActivatedRoute, useValue: route,
]
)
【讨论】:
【参考方案8】:Angular 9 版本
创建你的模拟:
const mockActivatedRoute =
parent:
paramMap: of(convertToParamMap(
organization: 'fakeOrganization'
))
;
提供它作为激活的路线
providers: [ provide: ActivatedRoute, useValue: mockActivatedRoute ]
测试一下
describe('ngOnInit', () =>
it('should get the route parameter', fakeAsync(() =>
component.ngOnInit();
expect(component.organization).toBe('fakeOrganization');
));
);'
正在测试的代码
ngOnInit(): void
this.route.parent.paramMap.subscribe(params =>
this.organization = params.get('organization');
);
【讨论】:
以上是关于出于测试目的,如何在 angular2 中模拟已激活的父路由?的主要内容,如果未能解决你的问题,请参考以下文章
Angular 2 单元测试组件,模拟 ContentChildren
如何在 Spring Security Bean 中模拟自定义 UserService 详细信息以进行单元测试?
Angular 2:如何在单元测试时模拟 ChangeDetectorRef