Mocking router.events.subscribe() Angular2

前端 未结 6 1181
生来不讨喜
生来不讨喜 2020-12-30 00:03

In my app.component.ts I have the following ngOnInit function:

ngOnInit() {
    this.sub = this.router.events.subscribe(e => {
      if (e instanceof Navi         


        
相关标签:
6条回答
  • 2020-12-30 00:34

    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();
      });
    }
    
    0 讨论(0)
  • 2020-12-30 00:40

    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);
      });
    
    });
    
    0 讨论(0)
  • 2020-12-30 00:46

    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

    0 讨论(0)
  • 2020-12-30 00:49

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

    0 讨论(0)
  • 2020-12-30 00:49

    The Angular Testing Documentation shows how to do this using a 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('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');
    });
    
    0 讨论(0)
  • 2020-12-30 00:55

    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 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

    0 讨论(0)
提交回复
热议问题