如何对依赖于 ActivatedRoute 参数的组件进行单元测试?

我正在单元测试一个用于编辑对象的组件。该对象具有唯一的 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服务的模拟,所以不用担心。

103802 次浏览

我知道该怎么做了!

因为 ActivatedRoute是一个服务,所以可以为它建立一个模拟服务。我们将这个模拟服务称为 MockActivatedRoute。我们将在 MockActivatedRoute中扩展 ActivatedRoute,具体如下:

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<>

Then, as any other mock service, just initialize it and override the provider for the component.

祝你好运!

下面是我如何在角度2.0最新测试它..。

import { ActivatedRoute, Data } from '@angular/router';

以及“供应商”部分

{
provide: ActivatedRoute,
useValue: {
data: {
subscribe: (fn: (value: Data) => void) => fn({
yourData: 'yolo'
})
}
}
}

最简单的方法是使用 useValue属性并提供一个您想要模拟的值的可观察值。

RxJS < 6

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})
}
}

Just add a mock of the 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: () => {} } }
};
}

对于一些人工作的角度 > 5,如果 Observer able.of () ; 不工作,那么他们可以通过从‘ rxjs’导入{ of }来使用 just of () ;

在角8 + 有一个 RouterTestingModule,你可以使用它来访问组件的 ActivatedRoute或者 Router。还可以将路由传递到 RouterTestingModule,并为请求的路由方法创建间谍。

For example in my component I have:

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”部分:

  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 弹珠。它可以节省一些时间,也不需要维护一个额外的模拟类。 希望它是有用的,它是有意义的。

在为路由路径创建测试套件时遇到了同样的问题:

{
path: 'edit/:property/:someId',
component: YourComponent,
resolve: {
yourResolvedValue: YourResolver
}
}

在组件中,我将传递的属性初始化为:

ngOnInit(): void {
this.property = this.activatedRoute.snapshot.params.property;
...
}

在运行测试时,如果没有在模拟 ActivatedRoute“ useValue”中传递属性值,那么在使用“ fixture.dettChanges ()”检测更改时将得到未定义的结果。这是因为 ActivatedRoute 的模拟值不包含属性 params.property。然后,模拟 useValue 需要这些参数,以便 fixture 在组件中初始化‘ 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');
....
});

Now, lets say you would like to test a different property value, then you can update your mock ActivatedRoute as:

  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');
});

希望这个能帮上忙!

在测试类中添加提供程序:

{
provide: ActivatedRoute,
useValue: {
paramMap: of({ get: v => { return { id: 123 }; } })
}
}

到目前为止,所有其他答案都只提供了路由参数的值。如果您想测试路由更改触发器本身,该怎么办?您可以在您的测试中提供一个 Subject 及其 Observer,这样您就可以使用 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);
}));

This tests the triggered action after the route change and makes sure it is activated.

角度11: 把这个添加到你的规范文件中

imports: [
RouterTestingModule.withRoutes([])
],

这帮助我出来只有一行,其他你需要模拟提供者

您可以使用 beforeAll函数来完成这项工作。由于 beforeAll在所有 beforeEach函数之前调用,因此可以在编译组件之前更改成员变量。

describe('MyComponent', () => {


let fakeActivatedRoute = {
paramMap: of(convertToParamMap({ id: '123' })),
queryParamMap: of(convertToParamMap({ query: 'active' }))};




beforeEach(async() => {
await TestBed.configureTestingModule({


providers: [
...
{ provide: ActivatedRoute, useValue: fakeActivatedRoute }],
}).compileComponents();
});
});


describe('id is present in route', () => {
beforeAll(() => {
fakeActivatedRoute.paramMap =
of(convertToParamMap({ id: '123' }));
fakeActivatedRoute.queryParamMap =
of(convertToParamMap({ query: '' }));
});


it('should call service to look up id', () => {
...
});
});


describe('id is not present in route', () => {
beforeAll(() => {
fakeActivatedRoute.paramMap =
of(convertToParamMap({ id: '' }));
fakeActivatedRoute.queryParamMap =
of(convertToParamMap({ query: '' }));
});


it('should not call service to look up id', () => {
...
});
});


describe('query is present in route', () => {
beforeAll(() => {
fakeActivatedRoute.paramMap =
of(convertToParamMap({ id: '123' }));
fakeActivatedRoute.queryParamMap =
of(convertToParamMap({ query: 'inactive' }));
});


it('should call service to look up the inactive id', () => {
...
});
});
});