[英]Mocking router.events.subscribe() Angular2
In my app.component.ts I have the following ngOnInit function:在我的 app.component.ts 中,我有以下 ngOnInit function:
ngOnInit() {
this.sub = this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
if (!e.url.includes('login')) {
this.loggedIn = true;
} else {
this.loggedIn = false;
}
}
});
}
Currently I'm testing if the sub is not null but I want to test the function with a 100% coverage.目前我正在测试 sub 是否不是 null,但我想测试 function 的覆盖率为 100%。
I want to mock the router object so that I can simulate the URL and then test if the this.loggedIn is correctly set.我想模拟路由器 object 以便我可以模拟 URL 然后测试 this.loggedIn 是否设置正确。
How would I proceed to mock this function?我将如何继续模拟这个 function? I tried it but I don't know how I would take this on with the callback involved and with the NavigationEnd.
我试过了,但我不知道如何处理涉及的回调和 NavigationEnd。
I have found the answer, if someone is looking for it:我找到了答案,如果有人正在寻找它:
import { NavigationEnd } from '@angular/router';
import { Observable } from 'rxjs/Observable';
class MockRouter {
public ne = new NavigationEnd(0, 'http://localhost:4200/login', 'http://localhost:4200/login');
public events = new Observable(observer => {
observer.next(this.ne);
observer.complete();
});
}
class MockRouterNoLogin {
public ne = new NavigationEnd(0, 'http://localhost:4200/dashboard', 'http://localhost:4200/dashboard');
public events = new Observable(observer => {
observer.next(this.ne);
observer.complete();
});
}
I created a version of the router stub from Angular docs that uses this method to implement NavigationEnd event for testing:我从 Angular 文档创建了一个路由器存根版本,它使用此方法实现 NavigationEnd 事件以进行测试:
import {Injectable} from '@angular/core'; import { NavigationEnd } from '@angular/router'; import {Subject} from "rxjs"; @Injectable() export class RouterStub { public url; private subject = new Subject(); public events = this.subject.asObservable(); navigate(url: string) { this.url = url; this.triggerNavEvents(url); } triggerNavEvents(url) { let ne = new NavigationEnd(0, url, null); this.subject.next(ne); } }
The accepted answer is correct but this is a bit simpler, you can replace接受的答案是正确的,但这有点简单,您可以替换
public ne = new NavigationEnd(0, 'http://localhost:4200/login', 'http://localhost:4200/login');
public events = new Observable(observer => {
observer.next(this.ne);
observer.complete();
});
by:经过:
public events = Observable.of( new NavigationEnd(0, 'http://localhost:4200/login', 'http://localhost:4200/login'));
And find below a full test file to test the function in the question:并在下面找到一个完整的测试文件来测试问题中的功能:
import { NO_ERRORS_SCHEMA } from '@angular/core';
import {
async,
TestBed,
ComponentFixture
} from '@angular/core/testing';
/**
* Load the implementations that should be tested
*/
import { AppComponent } from './app.component';
import { NavigationEnd, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
class MockServices {
// Router
public events = Observable.of( new NavigationEnd(0, 'http://localhost:4200/login', 'http://localhost:4200/login'));
}
describe(`App`, () => {
let comp: AppComponent;
let fixture: ComponentFixture<AppComponent>;
let router: Router;
/**
* async beforeEach
*/
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AppComponent ],
schemas: [NO_ERRORS_SCHEMA],
providers: [
{ provide: Router, useClass: MockServices },
]
})
/**
* Compile template and css
*/
.compileComponents();
}));
/**
* Synchronous beforeEach
*/
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
comp = fixture.componentInstance;
router = fixture.debugElement.injector.get( Router);
/**
* Trigger initial data binding
*/
fixture.detectChanges();
});
it(`should be readly initialized`, () => {
expect(fixture).toBeDefined();
expect(comp).toBeDefined();
});
it('ngOnInit() - test that this.loggedIn is initialised correctly', () => {
expect(comp.loggedIn).toEqual(true);
});
});
This is a really old question but I just came across it looking for something better than what i have and in my case i need to test several different events.这是一个非常古老的问题,但我只是在寻找比我拥有的更好的东西时遇到它,就我而言,我需要测试几个不同的事件。 My basic approach was just to change the Router.events to a non read only value like
我的基本方法是将 Router.events 更改为非只读值,例如
(router as any).events = new BehaviorSubject<any>(null);
fixture.detectChanges();
router.events.next(new NavigationEnd(0, 'http://localhost:4200/login',
'http://localhost:4200/login'));
expect(comp.loggedIn).toEqual(true);
Hope maybe that helps someone.希望这可能对某人有所帮助。 I couldn't find an easier solution after looking around
环顾四周后,我找不到更简单的解决方案
ReplaySubject<RouterEvent>
to mock the router.events
ReplaySubject<RouterEvent>
模拟router.events
filter
instead of instanceof
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 routerEventReplaySubject: ReplaySubject<RouterEvent>;
let routerMock;
beforeEach(() => {
routerEventReplaySubject = new ReplaySubject<RouterEvent>(1);
routerMock = {
events: routerEventReplaySubject.asObservable()
};
TestBed.configureTestingModule({
providers: [
{provide: Router, useValue: routerMock}
]
});
service = TestBed.inject(RouteEventService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('subscribeToEventUrl should return route equals to mock url on firing', () => {
it('NavigationEnd', () => {
const result: Observable<string> = service.subscribeToRouterEventUrl();
const url = '/mock';
result.subscribe((route: string) => {
expect(route).toEqual(url);
});
routerEventReplaySubject.next(new NavigationEnd(1, url, 'redirectUrl'));
});
it('NavigationStart', () => {
const result: Observable<string> = service.subscribeToRouterEventUrl();
const url = '/mock';
result.subscribe((route: string) => {
expect(route).toBeNull();
});
routerEventReplaySubject.next(new NavigationStart(1, url, 'imperative', null));
});
});
});
The previous example public events = Observable.of( new NavigationEnd(0, 'http://localhost..'));
前面的例子
public events = Observable.of( new NavigationEnd(0, 'http://localhost..'));
does not seem to work according to Karma which complains about:根据 Karma 的抱怨,它似乎不起作用:
Failed: undefined is not an object (evaluating 'router.routerState.root') rootRoute@ http://localhost:9876/_karma_webpack_/vendor.bundle.js
失败:未定义不是对象(评估'router.routerState.root')rootRoute@ http://localhost:9876/_karma_webpack_/vendor.bundle.js
Despite (mocked) Router instance events ' subscription callback has been running successfully in ngOninit()
of original app.component.ts, ie main application component under testing by Karma:尽管(模拟)路由器实例事件的订阅回调已在原始 app.component.ts 的
ngOninit()
中成功运行,即 Karma 正在测试的主要应用程序组件:
this.sub = this.router.events.subscribe(e => { // successful execution across Karma
Indeed, the way Router has been mocked sort of looks incomplete, inaccurate as a structure from Karma's prospective: because of router.routerState
that turns out to be undefined at run time.事实上,Router 被嘲笑的方式看起来有点不完整,从 Karma 的预期来看是不准确的结构:因为
router.routerState
在运行时被证明是未定义的。
Here is how Angular Router has been "stubbed" exactly on my side, including RoutesRecognized
events articifically baked as Observable
s in my case:以下是 Angular Router 是如何在我这边被“存根”的,包括
RoutesRecognized
事件,在我的例子中被人为地烘焙为Observable
:
class MockRouter {
public events = Observable.of(new RoutesRecognized(2 , '/', '/',
createRouterStateSnapshot()));
}
const createRouterStateSnapshot = function () {
const routerStateSnapshot = jasmine.createSpyObj('RouterStateSnapshot',
['toString', 'root']);
routerStateSnapshot.root = jasmine.createSpyObj('root', ['firstChild']);
routerStateSnapshot.root.firstChild.data = {
xxx: false
};
return <RouterStateSnapshot>routerStateSnapshot;
};
to fit what ngOnInit()
body expects, requiring RoutesRecognized
event with deep structure:为了满足
ngOnInit()
主体的期望,需要具有深层结构的RoutesRecognized
事件:
ngOnInit() {
this.router.events.filter((event) => {
return event instanceof RoutesRecognized;
}).subscribe((event: RoutesRecognized) => {
// if (!event.state.root.firstChild.data.xxx) {
// RoutesRecognized event... to be baked from specs mocking strategy
});
}
Recap / summary of my <package.json> content:回顾/总结我的 <package.json> 内容:
angular/router: 5.2.9, karma: 2.0.2, jasmine-core: 2.6.4, karma-jasmine: 1.1.2
角度/路由器:5.2.9,业力:2.0.2,茉莉花核:2.6.4,业力-茉莉花:1.1.2
The Angular Testing Documentation shows how to do this using a Jasmine spy: Angular 测试文档展示了如何使用 Jasmine 间谍来做到这一点:
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
const heroServiceSpy = jasmine.createSpyObj('HeroService', ['getHeroes']);
TestBed.configureTestingModule({
providers: [
{ provide: HeroService, useValue: heroServiceSpy },
{ provide: Router, useValue: routerSpy }
]
})
... ...
it('should tell ROUTER to navigate when hero clicked', () => {
heroClick(); // trigger click on first inner <div class="hero">
// args passed to router.navigateByUrl() spy
const spy = router.navigateByUrl as jasmine.Spy;
const navArgs = spy.calls.first().args[0];
// expecting to navigate to id of the component's first hero
const id = comp.heroes[0].id;
expect(navArgs).toBe('/heroes/' + id,
'should nav to HeroDetail for first hero');
});
The easiest method is probably this:最简单的方法可能是这样的:
(router.events as any) = new BehaviorSubject(
new NavigationEnd(0, 'http://localhost:4200/plain', `http://localhost:4200/plain`)
);
If you are using Jest for testing and you are detecting the NavigationEnd
event of the Router
in ngOnInit()
of a component, make sure to run the test in the waitForAsync()
test method.如果您使用Jest进行测试,并且在组件的
ngOnInit()
中检测到Router
的NavigationEnd
事件,请确保在waitForAsync()
测试方法中运行测试。
Here is an example:这是一个例子:
export class CatchPageComponent implements OnInit {
constructor(private myService: MyService, private router: Router) {}
ngOnInit(): void {
this.router.events
.pipe(
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
take(1) //needed to prevent infinite loop since we are triggering more navigation events in the subscription
)
.subscribe((navEnd) => {
const url = new URL(navEnd.urlAfterRedirects, 'http://localhost:8080'); //base is not relevant, just to be able to create URL to parse
const path = url.pathname;
if ('/fancy' === path) {
this.myService.myFunction()
} else {
this.router.navigate(['plain']);
}
});
}
}
describe('CatchPageComponent', () => {
let component: CatchPageComponent;
let fixture: ComponentFixture<CatchPageComponent>;
let myService: MyService;
let router: Router;
beforeEach(async () => {
await TestBed.configureTestingModule({
providers: [
MyService,
],
imports: [
CatchPageModule,
RouterTestingModule,
],
declarations: [CatchPageComponent],
}).compileComponents();
fixture = TestBed.createComponent(CatchPageComponent);
router = TestBed.inject(Router);
myService = TestBed.inject(MyService);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should call myService when route is fancy', waitForAsync(() => {
//simple mock of the NavigationEnd event
(router.events as any) = new BehaviorSubject(
new NavigationEnd(0, 'http://localhost:4200/fancy', `http://localhost:4200/fancy`)
);
const myFunctionSpy = jest.spyOn(myService, 'myFunction');
component.ngOnInit();
expect(myFunctionSpy).toHaveBeenCalledTimes(1);
}));
});
EDIT 1:编辑 1:
So I have realized this.router.navigate(['plain']);
所以我已经意识到
this.router.navigate(['plain']);
kept the second test hanging.保持第二次测试挂起。 If you need it in your implementation, you should mock it either in the
beforeEach()
method or in a specific test like this jest.spyOn(router, 'navigate').mockImplementation(() => Promise.resolve(true));
如果您在实现中需要它,您应该在
beforeEach()
方法或像这样的特定测试中模拟它jest.spyOn(router, 'navigate').mockImplementation(() => Promise.resolve(true));
Here it is:这里是:
it('should redirect to plain page', waitForAsync(() => {
jest.spyOn(router, 'navigate').mockImplementation(() => Promise.resolve(true)); //mocking navigate
(router.events as any) = new BehaviorSubject(
new NavigationEnd(0, 'http://localhost:4200/plain', `http://localhost:4200/plain`)
);
component.ngOnInit();
expect(router.navigate).toHaveBeenCalledWith(['plain']);
}));
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.