提问人:kriskanya 提问时间:10/20/2018 最后编辑:kriskanya 更新时间:1/13/2021 访问量:26376
Angular 6 - 如何在单元测试中模拟 router.events url 响应
Angular 6 - how to mock router.events url response in unit test
问:
正如标题所示,我需要在单元测试中模拟。router.events
在我的组件中,我做了一些正则表达式来抓取 url 中斜杠之间的第一个文本实例;例如,/pdp/
constructor(
private route: ActivatedRoute,
private router: Router,
}
this.router.events.pipe(takeUntil(this.ngUnsubscribe$))
.subscribe(route => {
if (route instanceof NavigationEnd) {
// debugger
this.projectType = route.url.match(/[a-z-]+/)[0];
}
});
我的单元测试在构建组件时出错:。当我在调试器中注释时,似乎没有正确设置,但我在单元测试本身中设置了它。我已经用了几种不同的方式(受这篇文章的启发:模拟 router.events.subscribe()、Angular2 等)。Cannot read property '0' of null
route
第一次尝试:
providers: [
{
provide: Router,
useValue: {
events: of(new NavigationEnd(0, '/pdp/project-details/4/edit', 'pdp/project-details/4/edit'))
}
},
// ActivatedRoute also being mocked
{
provide: ActivatedRoute,
useValue: {
snapshot: { url: [{ path: 'new' }, { path: 'edit' }] },
parent: {
parent: {
snapshot: {
url: [
{ path: 'pdp' }
]
}
}
}
}
}
]
第二次尝试(基于上述帖子):
class MockRouter {
public events = of(new NavigationEnd(0, '/pdp/project-details/4/edit', '/pdp/project-details/4/edit'))
}
providers: [
{
provide: Router,
useClass: MockRouter
}
]
第三次尝试(也基于上面的帖子):
class MockRouter {
public ne = new NavigationEnd(0, '/pdp/project-details/4/edit', '/pdp/project-details/4/edit');
public events = new Observable(observer => {
observer.next(this.ne);
observer.complete();
});
}
providers: [
{
provide: Router,
useClass: MockRouter
}
]
第四次尝试:
beforeEach(() => {
spyOn((<any>component).router, 'events').and.returnValue(of(new NavigationEnd(0, '/pdp/project-details/4/edit', 'pdp/project-details/4/edit')))
...
第五次尝试:
beforeEach(() => {
spyOn(TestBed.get(Router), 'events').and.returnValue(of({ url:'/pdp/project-details/4/edit' }))
...
在上述所有情况下,未设置;对象等于:route
NavigationEnd
{ id: 1, url: "/", urlAfterRedirects: "/" }
思潮?
答:
17赞
kriskanya
10/23/2018
#1
这是我想出的答案。我想我只是没有将第一种方法(上面)扩展得足够远:
import { of } from 'rxjs';
import { NavigationEnd, Router } from '@angular/router';
providers: [
{
provide: Router,
useValue: {
url: '/non-pdp/phases/8',
events: of(new NavigationEnd(0, 'http://localhost:4200/#/non-pdp/phases/8', 'http://localhost:4200/#/non-pdp/phases/8')),
navigate: jasmine.createSpy('navigate')
}
}
]
评论
0赞
Deunz
3/2/2020
但是这个路由器无法导航,所以你不能测试这样的场景,导航到“”应该重定向到/home,例如
31赞
daniel-sc
4/9/2019
#2
它可以像以下一样简单:
TestBed.configureTestingModule({
imports: [RouterTestingModule]
}).compileComponents()
...
const event = new NavigationEnd(42, '/', '/');
(TestBed.inject(Router).events as Subject<Event>).next(event);
评论
10赞
Richard Collette
6/8/2019
TS2339 属性“next”在类型“Observable<event>上不存在
1赞
daniel-sc
6/11/2019
@RichardCollette:在执行测试时,您认为这是衬里错误还是实际错误?您运行的是哪个 Angular 版本?(如果出现 liniting 错误,可以忽略它(强制转换为any
)
0赞
Richard Collette
6/12/2019
这是打字稿编译器错误,而不是 linting 错误。虽然返回的 Router.events 类型可以同时实现 Observer 和 Observable 接口,但 events 属性的公共接口是 Observable,它不会公开 next()。如果将来返回的对象不是观察者,则将来存在中断性变更的风险。使用 Angular 7, TS 3.2.4
2赞
daniel-sc
6/13/2019
@RichardCollette您对未来发生重大更改的风险是正确的。关于编译错误:我无法理解 - 该方法具有返回类型,因此不能进行任何编译时检查(除非您专门将结果强制转换为)。TestBed.get
any
Router
0赞
Deunz
3/2/2020
就我而言,组件中的订阅确实是由这段代码触发的,但是,我的测试不会等待订阅解决,fixture.whenStable 永远不会解析,我尝试了所有有异步、fakeAsync 等......
2赞
Leonardo
6/27/2019
#3
希望这能有所帮助,这就是我所做的:
const eventsStub = new BehaviorSubject<Event>(null);
export class RouterStub {
events = eventsStub;
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
imports: [RouterTestingModule],
providers: [
{ provide: HttpClient, useValue: {} },
{ provide: Router, useClass: RouterStub },
Store,
CybermetrieCentralizedService,
TileMenuComponentService,
HttpErrorService
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
router = TestBed.get(Router);
tileMenuService = TestBed.get(TileMenuComponentService);
component = fixture.componentInstance;
}));
然后在测试中我这样做了:
it('should close the menu when navigation to dashboard ends', async(() => {
spyOn(tileMenuService.menuOpened, 'next');
component.checkRouterEvents();
router.events.next(new NavigationEnd (1, '/', '/'));
expect(tileMenuService.menuOpened.next).toHaveBeenCalledWith(false);
}));
这是为了验证在导航到路由“/”之后我执行了预期的指令
5赞
swandog
11/13/2019
#4
以为这可能会有所帮助......在模板中有一个带有 routerLinks 的页脚组件。得到未提供根的错误....不得不使用“真正的”路由器......但有一个间谍变量..并将 router.events 指向我的测试可观察对象,以便我可以控制导航事件
export type Spied<T> = { [Method in keyof T]: jasmine.Spy };
describe("FooterComponent", () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
let de: DebugElement;
const routerEvent$ = new BehaviorSubject<RouterEvent>(null);
let router: Spied<Router>;
beforeEach(async(() => {
TestBed.configureTestingModule({
providers: [provideMock(SomeService), provideMock(SomeOtherService)],
declarations: [FooterComponent],
imports: [RouterTestingModule]
}).compileComponents();
router = TestBed.get(Router);
(<any>router).events = routerEvent$.asObservable();
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
de = fixture.debugElement;
}));
// component subscribes to router nav event...checking url to hide/show a link
it("should return false for showSomeLink()", () => {
routerEvent$.next(new NavigationEnd(1, "/someroute", "/"));
component.ngOnInit();
expect(component.showSomeLink()).toBeFalsy();
fixture.detectChanges();
const htmlComponent = de.query(By.css("#someLinkId"));
expect(htmlComponent).toBeNull();
});
评论
1赞
Deunz
3/2/2020
这有效,但是与此解决方案一起,我所有基于 Router.navigate(...) 的测试都失败了。如何同时测试 Router.navigate 和 router.event.subscribe() ?
4赞
hugomosh
7/9/2020
#5
我看到了这个替代方案,它适用于 @daniel-sc 的答案RouterTestingModule
const events = new Subject<{}>();
const router = TestBed.get(Router);
spyOn(router.events, 'pipe').and.returnValue(events);
events.next('Result of pipe');
评论
2赞
ShortM
1/18/2021
get() 已弃用。从 v9.0.0 开始,建议使用 inject()。
-1赞
Yuqiu G.
1/13/2021
#6
- 用于模拟
ReplaySubject<RouterEvent>
router.events
- 将其导出到服务,以便您可以更独立地独立于组件对其进行测试,并且其他组件可能也希望使用此服务;)
- 使用代替
filter
instanceof
源代码
import {Injectable} from '@angular/core';
import {NavigationEnd, Router, RouterEvent} from '@angular/router';
import {filter, map} from 'rxjs/operators';
import {Observable} from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class RouteEventService {
constructor(private router: Router) {
}
subscribeToRouterEventUrl(): Observable<string> {
return this.router.events
.pipe(
filter(event => event instanceof NavigationEnd),
map((event: RouterEvent) => event.url)
);
}
}
测试代码
import {TestBed} from '@angular/core/testing';
import {RouteEventService} from './route-event.service';
import {NavigationEnd, NavigationStart, Router, RouterEvent} from '@angular/router';
import {Observable, ReplaySubject} from 'rxjs';
describe('RouteEventService', () => {
let service: RouteEventService;
let routerEventRelaySubject: ReplaySubject<RouterEvent>;
let routerMock;
beforeEach(() => {
routerEventRelaySubject = new ReplaySubject<RouterEvent>(1);
routerMock = {
events: routerEventRelaySubject.asObservable()
};
TestBed.configureTestingModule({
providers: [
{provide: Router, useValue: routerMock}
]
});
service = TestBed.inject(RouteEventService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('subscribeToEventUrl should', () => {
it('return route equals to mock url on firing NavigationEnd', () => {
const result: Observable<string> = service.subscribeToRouterEventUrl();
const url = '/mock';
result.subscribe((route: string) => {
expect(route).toEqual(url);
});
routerEventRelaySubject.next(new NavigationEnd(1, url, 'redirectUrl'));
});
it('return route equals to mock url on firing NavigationStart', () => {
const result: Observable<string> = service.subscribeToRouterEventUrl();
const url = '/mock';
result.subscribe((route: string) => {
expect(route).toBeNull();
});
routerEventRelaySubject.next(new NavigationStart(1, url, 'imperative', null));
});
});
});
评论