【问题标题】:Mocking router.events.subscribe() Angular2模拟 router.events.subscribe() Angular2
【发布时间】:2016-11-23 08:10:52
【问题描述】:

在我的 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 是否不为空,但我想测试覆盖率 100% 的函数。

我想模拟路由器对象,以便我可以模拟 URL,然后测试 this.loggedIn 是否设置正确。

我将如何继续模拟这个函数?我试过了,但我不知道如何处理涉及的回调和 NavigationEnd。

【问题讨论】:

    标签: unit-testing angular mocking jasmine


    【解决方案1】:

    我已经找到答案了,如果有人正在寻找它:

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

    【讨论】:

    • 请提供有关您的 TestingModule 配置的信息。
    • 这个答案不完整。请提供使用说明。
    • 要让它工作provide模拟类到TestBedproviders: [{provide: Router, useClass: MockRouter]
    【解决方案2】:

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

    【讨论】:

    • 你知道如何模拟RoutesRecognized而不是NavigationEnd吗?
    【解决方案3】:

    接受的答案是正确的,但这有点简单,你可以替换

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

    【讨论】:

    • 感谢您提供完整文件。有帮助
    【解决方案4】:

    这是一个非常古老的问题,但我只是在寻找比我拥有的更好的东西时遇到它,就我而言,我需要测试几个不同的事件。我的基本方法是将 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】:
    • 使用ReplaySubject&lt;RouterEvent&gt;模拟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】:

      前面的例子public events = Observable.of( new NavigationEnd(0, 'http://localhost..')); 根据 Karma 似乎不起作用,抱怨

      失败:未定义不是一个对象(评估 'router.routerState.root') rootRoute@http://localhost:9876/_karma_webpack_/vendor.bundle.js

      尽管(模拟)路由器实例events的订阅回调已在原始app.component.ts的ngOninit()成功运行,即Karma正在测试的主要应用程序组件:

      this.sub = this.router.events.subscribe(e => { // successful execution across Karma
      

      事实上,从 Karma 的角度来看,Router 被模拟的方式看起来有点不完整,作为一个 结构 是不准确的:因为 router.routerState 在运行时是未定义的。

      以下是 Angular 路由器在我这边被“存根”的方式,包括 RoutesRecognized events 人工烘焙 在我的例子中是 Observables:

      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() body 的期望,需要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
         });
      }
      

      我的 内容的回顾/总结:

      角度/路由器:5.2.9, 业力:2.0.2, 茉莉花核:2.6.4, 业力茉莉花:1.1.2

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-05-10
          • 2017-09-15
          • 2017-03-14
          • 2016-04-18
          • 1970-01-01
          • 1970-01-01
          • 2017-09-08
          • 2023-03-06
          相关资源
          最近更新 更多