如何对依赖于 ActivatedRoute 参数的组件进行单元测试?
Posted
技术标签:
【中文标题】如何对依赖于 ActivatedRoute 参数的组件进行单元测试?【英文标题】:How to unit test a component that depends on parameters from ActivatedRoute? 【发布时间】:2016-11-16 07:13:56 【问题描述】:我正在对用于编辑对象的组件进行单元测试。该对象具有唯一的id
,用于从服务中托管的对象数组中获取特定对象。特定的id
是通过路由传递的参数获取的,特别是通过ActivatedRoute
类。
构造函数如下:
constructor(private _router:Router, private _curRoute:ActivatedRoute, private _session:Session)
ngOnInit()
this._curRoute.params.subscribe(params =>
this.userId = params['id'];
this.userObj = this._session.allUsers.filter(user => user.id.toString() === this.userId.toString())[0];
我想在这个组件上运行基本的单元测试。但是,我不确定如何注入id
参数,而组件需要这个参数。
顺便说一句:我已经模拟了Session
服务,所以不用担心。
【问题讨论】:
【参考方案1】:最简单的方法是使用useValue
属性并提供一个你想要模拟的值的Observable。
RxJS
import Observable from 'rxjs/Observable';
import 'rxjs/add/observable/of';
...
provide: ActivatedRoute,
useValue:
params: Observable.of(id: 123)
RxJS >= 6
import of from 'rxjs';
...
provide: ActivatedRoute,
useValue:
params: of(id: 123)
【讨论】:
Observable.of 对我来说不存在! :S 从 rxjs/Observable 导入 Observable 此代码在我的项目中出现此错误:Uncaught NetworkError: Failed to execute 'send' on 'XMLHttpRequest': Failed to load 'ng:///DynamicTestModule/HomeContentComponent.ngfactory.js'. at http://localhost:9876/_karma_webpack_/polyfills.bundle.js:2605
RxJs 6 of
应该单独使用。此外,您可能会使用 RouterTestingModule
而不是此答案的代码。
@BenRacicot 这个答案是在 RxJs 6 存在之前给出的。也改为说“改为这样做”提供了一个可以直接投票的答案。【参考方案2】:
在 Angular 8+ 中有 RouterTestingModule
,您可以使用它来访问组件的 ActivatedRoute
或 Router
。您也可以将路由传递给RouterTestingModule
,并为请求的路由方法创建间谍。
例如在我的组件中:
ngOnInit()
if (this.route.snapshot.paramMap.get('id')) this.editMode()
this.titleService.setTitle(`$this.pageTitle | $TAB_SUFFIX`)
在我的测试中,我有:
beforeEach(async(() =>
TestBed.configureTestingModule(
declarations: [ ProductLinePageComponent ],
schemas: [NO_ERRORS_SCHEMA],
imports: [
RouterTestingModule.withRoutes([])
],
)
.compileComponents()
))
beforeEach(() =>
router = TestBed.get(Router)
route = TestBed.get(ActivatedRoute)
)
稍后在“它”部分:
it('should update', () =>
const spyRoute = spyOn(route.snapshot.paramMap, 'get')
spyRoute.and.returnValue('21')
fixture = TestBed.createComponent(ProductLinePageComponent)
component = fixture.componentInstance
fixture.detectChanges()
expect(component).toBeTruthy()
expect(component.pageTitle).toBe('Edit Product Line')
expect(component.formTitle).toBe('Edit Product Line')
// here you can test the functionality which is triggered by the snapshot
)
以类似的方式,我认为您可以通过 jasmine 的 spyOnProperty
方法直接测试 paramMap
,方法是返回一个可观察对象或使用 rxjs 弹珠。它可能会节省一些时间,也不需要维护额外的模拟类。
希望它有用并且有意义。
【讨论】:
比维护一个额外的模拟要好得多,您可以轻松地在测试中设置不同的参数。谢谢! 这有帮助。你知道如何监视不同的参数: const dirName = this.route.snapshot.paramMap.get('dirName');常量 actionType = this.route.snapshot.paramMap.get('actionType');哪个机器人会监视 spyOn(route.snapshot.paramMap, 'get') ?我可以指定要听的键吗? 正如我上面提到的,我认为您可以使用 spyOnProperty 而不是 spyOn,例如 spyOnProperty(route.snapshot.paramMap.get, 'dirName')。如果我还没有完全回答你的问题,请不要犹豫告诉我。谢谢。 谢谢你,这很有帮助 ☺【参考方案3】:我已经想出了如何做到这一点!
由于ActivatedRoute
是一个服务,可以为它建立一个模拟服务。让我们将此模拟服务称为MockActivatedRoute
。我们将ActivatedRoute
扩展成MockActivatedRoute
,如下:
class MockActivatedRoute extends ActivatedRoute
constructor()
super(null, null, null, null, null);
this.params = Observable.of(id: "5");
super(null, ....)
行初始化超类,它有四个强制参数。但是,在这种情况下,我们不需要任何这些参数,因此我们将它们初始化为 null
值。我们需要的只是params
的值,即Observable<>
。因此,使用this.params
,我们覆盖params
的值并将其初始化为测试对象所依赖的参数的Observable<>
。
然后,与任何其他模拟服务一样,只需对其进行初始化并覆盖组件的提供者。
祝你好运!
【讨论】:
我现在正面临这个问题!但是,当我尝试使用super
或 Observable
时出现错误。这些是从哪里来的?
super()
是内置的。Observable
来自rxjs/Observable
或只是rxjs
,具体取决于您的版本。您可以使用import Observable from 'rxjs'
来获取它。
您接受了一个答案并发布了另一个答案...如果这是汉兰达(而且只能是一个),您“真正”选择了哪个答案,为什么?也就是说,我认为这基本上与您接受的 zmanc 的答案相同。您是否从设置这个 [稍微] 更复杂的模拟中发现了额外的价值?【参考方案4】:
这是我在 Angular 2.0 最新版本中测试它的方法...
import ActivatedRoute, Data from '@angular/router';
在提供者部分
provide: ActivatedRoute,
useValue:
data:
subscribe: (fn: (value: Data) => void) => fn(
yourData: 'yolo'
)
【讨论】:
能否提供提供程序部分的完整代码? 这是一个完整的单元测试类。 plnkr.co/edit/UeCKnJ2sCCpLLQcWqEGX?p=catalogue 如何在 ngOnDestroy 中测试退订 这将打破现实生活中的用例,因为您没有返回订阅并且您将无法在 ngOnDestroy 中使用调用 .unsubscribe()。 data: Observable.of(yourData: 'yolo') 会起作用。【参考方案5】:只需添加一个 ActivatedRoute 的模拟:
providers: [
provide: ActivatedRoute, useClass: MockActivatedRoute
]
...
class MockActivatedRoute
// here you can add your mock objects, like snapshot or parent or whatever
// example:
parent =
snapshot: data: title: 'myTitle ' ,
routeConfig: children: filter: () =>
;
【讨论】:
【参考方案6】:对于一些在 Angular > 5 上工作的人,if Observable.of();不起作用,那么他们可以通过从 'rxjs' 导入 import of 来使用 of();
【讨论】:
【参考方案7】:在为路由路径创建测试套件时遇到了同样的问题:
path: 'edit/:property/:someId',
component: YourComponent,
resolve:
yourResolvedValue: YourResolver
在组件中,我将传递的属性初始化为:
ngOnInit(): void
this.property = this.activatedRoute.snapshot.params.property;
...
运行测试时,如果您没有在模拟的 ActivatedRoute“useValue”中传递属性值,那么在使用“fixture.detectChanges()”检测更改时,您将得到未定义。这是因为 ActivatedRoute 的模拟值不包含属性 params.property。然后,模拟 useValue 需要具有这些参数,以便夹具初始化组件中的“this.property”。您可以将其添加为:
let fixture: ComponentFixture<YourComponent>;
let component: YourComponent;
let activatedRoute: ActivatedRoute;
beforeEach(done =>
TestBed.configureTestingModule(
declarations: [YourComponent],
imports: [ YourImportedModules ],
providers: [
YourRequiredServices,
provide: ActivatedRoute,
useValue:
snapshot:
params:
property: 'yourProperty',
someId: someId
,
data:
yourResolvedValue: data: mockResolvedData()
]
)
.compileComponents()
.then(() =>
fixture = TestBed.createComponent(YourComponent);
component = fixture.debugElement.componentInstance;
activatedRoute = TestBed.get(ActivatedRoute);
fixture.detectChanges();
done();
);
);
您可以开始测试,例如:
it('should ensure property param is yourProperty', async () =>
expect(activatedRoute.snapshot.params.property).toEqual('yourProperty');
....
);
现在,假设您想测试一个不同的属性值,那么您可以将模拟的 ActivatedRoute 更新为:
it('should ensure property param is newProperty', async () =>
activatedRoute.snapshot.params.property = 'newProperty';
fixture = TestBed.createComponent(YourComponent);
component = fixture.debugElement.componentInstance;
activatedRoute = TestBed.get(ActivatedRoute);
fixture.detectChanges();
expect(activatedRoute.snapshot.params.property).toEqual('newProperty');
);
希望这会有所帮助!
【讨论】:
它正在工作,我只是不知道为什么当我只将activatedRoute.snapshot 和fixture.detect.. 放入其中时它不起作用。但是当我复制你的代码时它的工作 你有什么想法,为什么我必须再次初始化它块中的夹具和组件,即使它已经在 beforeeach 中?【参考方案8】:角度 11: 将此添加到您的规范文件中
imports: [
RouterTestingModule.withRoutes([])
],
这只需一行就可以帮助我,其他你需要模拟提供者
【讨论】:
【参考方案9】:在测试类中添加提供者为:
provide: ActivatedRoute,
useValue:
paramMap: of( get: v => return id: 123 ; )
【讨论】:
【参考方案10】:到目前为止,所有其他答案仅提供路由参数的值。如果您想测试路由更改触发器本身怎么办?您可以在测试中为 ActivatedRoute 提供 Subject 及其 Observable,这样您就可以使用 source.next() 触发路由更改。
被测代码:
constructor(private readonly route: ActivatedRoute)
ngOnInit(): void
this.routeParamSubscription = this.route.params.subscribe((params) =>
if (params['id'])
this.loadDetails(params['id']);
);
测试代码:
let routeChangeSource: BehaviorSubject<Params>;
// In TestBed.configureTestingMethod
...
providers: [
provide: ActivatedRoute,
useValue:
params: routeChangeSource.asObservable()
]
...
it('loads data on route change', fakeAsync(() =>
const spy = spyOn(component, 'loadDetails').and.callThrough();
routeChangeSource.next( id: 99 );
tick();
expect(spy).toHaveBeenCalledOnceWith(99);
));
这会测试路由更改后触发的操作并确保它被激活。
【讨论】:
以上是关于如何对依赖于 ActivatedRoute 参数的组件进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章
Angular 8:无法实例化循环依赖 - ActivatedRoute
Angular 4 - 失败:无法解析 ActivatedRoute 的所有参数:(?, ?, ?, ?, ?, ?, ?, ?)
typescript 使用参数,数据和快照模拟ActivatedRoute。