Mocking router.events.subscribe() Angular2

亡梦爱人 提交于 2019-12-03 11:12:40

问题


In my app.component.ts I have the following 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.

I want to mock the router object so that I can simulate the URL and then test if the this.loggedIn is correctly set.

How would I proceed to mock this function? I tried it but I don't know how I would take this on with the callback involved and with the NavigationEnd.


回答1:


I have found the answer, if someone is looking for it:

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



回答2:


I created a version of the router stub from Angular docs that uses this method to implement NavigationEnd event for testing:

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



回答3:


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

});



回答4:


The previous example public events = Observable.of( new NavigationEnd(0, 'http://localhost..')); does not seem to work according to Karma which complains about:

Failed: undefined is not an object (evaluating '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, i.e main application component under testing by 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.

Here is how Angular Router has been "stubbed" exactly on my side, including RoutesRecognized events articifically baked as Observables in my case:

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() {
   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:

angular/router: 5.2.9, karma: 2.0.2, jasmine-core: 2.6.4, karma-jasmine: 1.1.2



来源:https://stackoverflow.com/questions/38475342/mocking-router-events-subscribe-angular2

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!