在我的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.loggedIn = false;
}
}
});
}
目前我正在测试sub是否为null但我想以100%的覆盖率测试该函数。
我想模拟路由器对象,以便我可以模拟URL,然后测试是否正确设置了this.loggedIn。
我将如何继续模拟此功能?我尝试了但是我不知道如何通过所涉及的回调和NavigationEnd来实现这一点。
答案 0 :(得分:23)
如果有人正在寻找,我找到了答案:
import {
addProviders,
async,
inject,
TestComponentBuilder,
ComponentFixture,
fakeAsync,
tick
} from '@angular/core/testing';
import { AppComponent } from './app.component';
import { Router, ROUTER_DIRECTIVES, NavigationEnd } from '@angular/router';
import { HTTP_PROVIDERS } from '@angular/http';
import { LocalStorage, WEB_STORAGE_PROVIDERS } from 'h5webstorage';
import { NavComponent } from '../nav/nav.component';
import { FooterComponent } from '../footer/footer.component';
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();
});
}
答案 1 :(得分:9)
我从Angular docs创建了一个版本的路由器存根,它使用这个方法来实现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);
}
}

答案 2 :(得分:5)
接受的答案是正确的但这有点简单,你可以替换
public ne = new NavigationEnd(0, 'http://localhost:4200/login', 'http://localhost:4200/login');
public events = new Observable(observer => {
observer.next(this.ne);
observer.complete();
});
由:
public events = Observable.of( new NavigationEnd(0, 'http://localhost:4200/login', 'http://localhost:4200/login'));
在下面找到一个完整的测试文件来测试问题中的函数:
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);
});
});
答案 3 :(得分:1)
上一个示例public events = Observable.of( new NavigationEnd(0, 'http://localhost..'));
似乎不符合Karma的抱怨抱怨:
失败:undefined不是对象(评估 &#39; router.routerState.root&#39;) rootRoute @ http://localhost:9876/_karma_webpack_/vendor.bundle.js
尽管(模拟)路由器实例事件&#39;订阅回调已在原始app.component.ts的ngOninit()
中成功运行,即由Karma测试的主要应用程序组件:
this.sub = this.router.events.subscribe(e => { // successful execution across Karma
事实上,路由器被嘲笑的方式看起来有点不完整,不准确,因为来自Karma的前瞻性结构:因为router.routerState
在运行时结果未定义时间。
以下是Angular Router如何&#34; stubbed&#34;完全在我身边,包括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;
};
以适应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
});
}
我的&lt; package.json&gt;的回顾/总结含量:
角度/路由器:5.2.9, 因果报应:2.0.2, 茉莉核心:2.6.4, karma-jasmine:1.1.2
答案 4 :(得分:1)
这是一个非常老的问题,但是我碰到了这个问题,正在寻找比我现有的更好的东西,就我而言,我需要测试几个不同的事件。我的基本方法是将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);
希望可能对某人有所帮助。环顾四周后,我找不到更简单的解决方案
答案 5 :(得分:1)
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));
});
});
});
答案 6 :(得分:0)
Angular Testing Documentation显示了如何使用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');
});