Unit testing 模拟路由器.events.subscribe()Angular2
在我的app.component.ts中,我有以下ngOnInit功能:Unit testing 模拟路由器.events.subscribe()Angular2,unit-testing,angular,mocking,jasmine,Unit Testing,Angular,Mocking,Jasmine,在我的app.component.ts中,我有以下ngOnInit功能: ngOnInit() { this.sub = this.router.events.subscribe(e => { if (e instanceof NavigationEnd) { if (!e.url.includes('login')) { this.loggedIn = true; } else { this.lo
ngOnInit() {
this.sub = this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) {
if (!e.url.includes('login')) {
this.loggedIn = true;
} else {
this.loggedIn = false;
}
}
});
}
目前我正在测试sub是否不为null,但我想测试覆盖率为100%的函数
我想模拟路由器对象,以便模拟URL,然后测试this.loggedIn是否正确设置
我将如何继续模拟此函数?我试过了,但我不知道如何处理涉及的回调和NavigationEnd。如果有人在寻找答案,我已经找到了答案:
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();
});
}
我根据Angular docs创建了一个路由器存根版本,该版本使用此方法实现NavigationEnd事件以进行测试:
从'@angular/core'导入{Injectable};
从'@angular/router'导入{NavigationEnd};
从“rxjs”导入{Subject};
@可注射()
出口级路由器{
公共网址;
私有主体=新主体();
public events=this.subject.asObservable();
导航(url:string){
this.url=url;
this.triggerNavEvents(url);
}
triggerNavEvents(url){
让ne=new NavigationEnd(0,url,null);
本。主题。下一个(ne);
}
}
接受的答案是正确的,但这稍微简单一点,您可以替换
public ne = new NavigationEnd(0, 'http://localhost:4200/login', 'http://localhost:4200/login');
public events = new Observable(observer => {
observer.next(this.ne);
observer.complete();
});
作者:
并在下面找到一个完整的测试文件来测试问题中的功能:
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);
});
});
从'@angular/core'导入{NO_ERRORS_SCHEMA};
进口{
异步,
试验台,
部件夹具
}来自“角度/核心/测试”;
/**
*加载应该测试的实现
*/
从“./app.component”导入{AppComponent};
从'@angular/Router'导入{NavigationEnd,Router};
从“rxjs/Observable”导入{Observable};
类模拟服务{
//路由器
公共事件=可观察到的(新导航结束(0,'http://localhost:4200/login', 'http://localhost:4200/login'));
}
描述(`App`,()=>{
让comp:AppComponent;
let夹具:组件夹具;
让路由器:路由器;
/**
*之前异步
*/
beforeach(异步(()=>{
TestBed.configureTestingModule({
声明:[AppComponent],
模式:[无错误模式],
供应商:[
{提供:路由器,useClass:MockServices},
]
})
/**
*编译模板和css
*/
.compileComponents();
}));
/**
*同步前置
*/
在每个之前(()=>{
fixture=TestBed.createComponent(AppComponent);
comp=夹具。组件状态;
路由器=fixture.debugElement.injector.get(路由器);
/**
*触发初始数据绑定
*/
fixture.detectChanges();
});
它(`should ready initialized`,()=>{
期望(fixture.toBeDefined();
期望(comp.toBeDefined();
});
它('ngOnInit()-测试this.loggedIn是否正确初始化',()=>{
expect(comp.loggedIn)、toEqual(true);
});
});
前面的示例公共事件=可观察的(新导航结束(0,'http://localhost..'));代码>似乎不符合报应,报应抱怨:
失败:未定义的不是对象(正在评估)
'router.routerState.root')
根路径@
尽管(模拟)路由器实例事件,但订阅回调已在原始app.component.ts的ngOninit()
中成功运行,即Karma正在测试的主要应用程序组件:
this.sub = this.router.events.subscribe(e => { // successful execution across Karma
事实上,路由器被嘲笑的方式看起来有点不完整,从Karma的角度来看是不准确的:因为Router.routerState
在运行时没有定义
以下是角度路由器在我这边是如何被“截短”的,包括路由识别事件在我的情况下被人为烘焙为可观察的
s:
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;
};
重述/总结我的内容:
角度/路由器:5.2.9,
业力:2.0.2,
茉莉花芯:2.6.4,
因果报应茉莉花:1.1.2
这是一个非常古老的问题,但我只是在寻找比我拥有的更好的东西时遇到它,在我的情况下,我需要测试几个不同的事件。我的基本方法是将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);
(路由器如有)。事件=新行为主体(空);
fixture.detectChanges();
router.events.next(新导航结束(0,'http://localhost:4200/login',
'http://localhost:4200/login'));
expect(comp.loggedIn)、toEqual(true);
希望这能帮助别人。环顾四周后,我找不到一个更简单的解决方案。演示了如何使用Jasmine spy执行此操作:
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);
const heroServiceSpy = jasmine.createSpyObj('HeroService', ['getHeroes']);
TestBed.configureTestingModule({
providers: [
{ provide: HeroService, useValue: heroServiceSpy },
{ provide: Router, useValue: routerSpy }
]
})
it('当英雄点击时应该告诉路由器导航',()=>{
heroClick();//触发单击第一个内部
//参数传递给router.navigateByUrl()间谍
const spy=router.navigateByUrl作为jasmine.spy;
const navArgs=spy.calls.first().args[0];
//希望导航到组件的第一个英雄的id
const id=comp.heros[0].id;
expect(navArgs).toBe('/heroses/'+id,
“应该为第一个英雄导航到英雄细节”);
});
- 使用
ReplaySubject
模拟router.events
- 将其导出到服务,以便您可以更独立于组件对其进行测试,其他组件也可能希望使用此服务;)李>
- 使用
filter
而不是instanceof
- 然后为组件添加测试,我认为这很简单,对吗:)
源代码
从'@angular/core'导入{Injectable};
从'@angular/Router'导入{NavigationEnd,Router,RouterEvent};
从“rxjs/operators”导入{filter,map};
从“rxjs”导入{Observable};
@注射的({
providedIn:'根'
})
导出类RouteEventService{
构造函数(专用路由器:路由器){
}
subscribeToRouterEventUrl():可观察{
返回此.router.events
.烟斗(
过滤器(事件=>NavigationEnd的事件实例),
映射((事件:RouterEvent)=>event.url)
);
}
}
测试代码
从'@angular/core/testing'导入{TestBed};
从“”导入{RouteEventService}/
(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);
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');
});
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));
});
});
});