Angular 6-如何在单元测试中模拟router.events url响应

时间:2018-10-19 23:24:27

标签: angular typescript unit-testing

正如标题所示,我需要在单元测试中模拟router.events

在我的组件中,我做了一些正则表达式来获取url中斜线之间的文本的第一个实例;例如/pdp/

  constructor(
    private route: ActivatedRoute,
    private router: Router,
  }

this.router.events.pipe(takeUntil(this.ngUnsubscribe$))
      .subscribe(route => {
        if (route instanceof NavigationEnd) {
        // debugger
          this.projectType = route.url.match(/[a-z-]+/)[0];
        }
      });

构建组件时,我的单元测试出了错误:Cannot read property '0' of null。当我在调试器中添加注释时,route似乎设置不正确,但是我在单元测试中对其进行了设置。我已经通过几种不同的方式完成了此操作(此帖子的灵感来自Mocking router.events.subscribe() Angular2等)。

第一次尝试:

providers: [
        {
          provide: Router,
          useValue: {
            events: of(new NavigationEnd(0, '/pdp/project-details/4/edit', 'pdp/project-details/4/edit'))
          }
        },
        // ActivatedRoute also being mocked
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: { url: [{ path: 'new' }, { path: 'edit' }] },
            parent: {
              parent: {
                snapshot: {
                  url: [
                    { path: 'pdp' }
                  ]
                }
              }
            }
          }
        }
]

第二次尝试(基于以上信息):

class MockRouter {
  public events = of(new NavigationEnd(0, '/pdp/project-details/4/edit', '/pdp/project-details/4/edit'))
}

providers: [
        {
          provide: Router,
          useClass: MockRouter
        }
]

第三次尝试(也基于上面的帖子):

class MockRouter {
  public ne = new NavigationEnd(0, '/pdp/project-details/4/edit', '/pdp/project-details/4/edit');
  public events = new Observable(observer => {
    observer.next(this.ne);
    observer.complete();
  });
}

providers: [
        {
          provide: Router,
          useClass: MockRouter
        }
]

第四次尝试:

beforeEach(() => {
    spyOn((<any>component).router, 'events').and.returnValue(of(new NavigationEnd(0, '/pdp/project-details/4/edit', 'pdp/project-details/4/edit')))
...

第五次尝试:

beforeEach(() => {
    spyOn(TestBed.get(Router), 'events').and.returnValue(of({ url:'/pdp/project-details/4/edit' }))
...

在上述所有情况下,均未设置routeNavigationEnd对象等于:

{ id: 1, url: "/", urlAfterRedirects: "/" }

有想法吗?

6 个答案:

答案 0 :(得分:4)

这是我想出的答案。我想我只是没有将第一种方法(上述)扩展得足够远:

import { of } from 'rxjs';
import { NavigationEnd, Router } from '@angular/router';

providers: [
    {
      provide: Router,
      useValue: {
        url: '/non-pdp/phases/8',
        events: of(new NavigationEnd(0, 'http://localhost:4200/#/non-pdp/phases/8', 'http://localhost:4200/#/non-pdp/phases/8')),
        navigate: jasmine.createSpy('navigate')
      }
    }
]

答案 1 :(得分:2)

我看到了这种替代方法,它可以像{daniel-sc的答案那样使用RouterTestingModule

   const events = new Subject<{}>();
   const router = TestBed.get(Router);
   spyOn(router.events, 'pipe').and.returnValue(events);
   events.next('Result of pipe');

答案 2 :(得分:1)

它可以很简单:

TestBed.configureTestingModule({
  imports: [RouterTestingModule]
}).compileComponents()

...

const event = new NavigationEnd(42, '/', '/');
TestBed.get(Router).events.next(event);

答案 3 :(得分:0)

希望这会有所帮助,这就是我所做的:

const eventsStub = new BehaviorSubject<Event>(null);
    export class RouterStub {
      events = eventsStub;
    }

    beforeEach(async(() => {
        TestBed.configureTestingModule({
          declarations: [AppComponent],
          imports: [RouterTestingModule],
          providers: [
            { provide: HttpClient, useValue: {} },
            { provide: Router, useClass: RouterStub },
            Store,
            CybermetrieCentralizedService,
            TileMenuComponentService,
            HttpErrorService
          ],
          schemas: [NO_ERRORS_SCHEMA]
        }).compileComponents();
        fixture = TestBed.createComponent(AppComponent);
        router = TestBed.get(Router);
        tileMenuService = TestBed.get(TileMenuComponentService);
        component = fixture.componentInstance;
      }));

然后在测试中我做到了:

     it('should close the menu when navigation to dashboard ends', async(() => {
        spyOn(tileMenuService.menuOpened, 'next');
        component.checkRouterEvents();
        router.events.next(new NavigationEnd (1, '/', '/'));
        expect(tileMenuService.menuOpened.next).toHaveBeenCalledWith(false);
      }));

这是为了验证导航到路由“ /”之后我是否执行了预期的指令

答案 4 :(得分:0)

这可能会帮助...在模板中具有带有routerLinks的页脚组件。正在获得未提供根目录的错误。。。不得不使用“真正的”路由器。但是有一个间谍变量..并将router.events指向我的测试可观察到,以便我可以控制导航事件

export type Spied<T> = { [Method in keyof T]: jasmine.Spy };

describe("FooterComponent", () => {
   let component: FooterComponent;
   let fixture: ComponentFixture<FooterComponent>;
   let de: DebugElement;
   const routerEvent$ = new BehaviorSubject<RouterEvent>(null);
   let router: Spied<Router>;

 beforeEach(async(() => {
    TestBed.configureTestingModule({
        providers: [provideMock(SomeService), provideMock(SomeOtherService)],
        declarations: [FooterComponent],
        imports: [RouterTestingModule]
    }).compileComponents();

    router = TestBed.get(Router);
    (<any>router).events = routerEvent$.asObservable();

    fixture = TestBed.createComponent(FooterComponent);
    component = fixture.componentInstance;
    de = fixture.debugElement;
}));
// component subscribes to router nav event...checking url to hide/show a link
it("should return false for showSomeLink()", () => {
    routerEvent$.next(new NavigationEnd(1, "/someroute", "/"));
    component.ngOnInit();
    expect(component.showSomeLink()).toBeFalsy();
    fixture.detectChanges();
    const htmlComponent = de.query(By.css("#someLinkId"));
    expect(htmlComponent).toBeNull();
});

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