Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular Unit test router.events

I have a component that updates a variable on specific router events. How do I unit test to make sure the code below works correctly?

router.events.subscribe(event => {
  if (event instanceof NavigationStart) {
    this.isLoading = true;
  } else if (event instanceof NavigationEnd || event instanceof NavigationCancel) {
    this.isLoading = false;
  } else if (event instanceof NavigationError) {
    this.isLoading = false;
    // console.error('effect error: ', error);
  }
});

Router stub

class MockRouter {
    navigate = jasmine.createSpy('navigate');
    start = new NavigationStart(0, '/home');
    end = new NavigationEnd(1, '/home', '/dashboard');
    events = new Observable(observer => {
      observer.next(this.start);
      observer.next(this.end);
      observer.complete();
    });
  }
like image 698
Pam Halligan-Sims Avatar asked Dec 11 '17 13:12

Pam Halligan-Sims


People also ask

How to write test cases for routing in Angular?

We can test routing in Angular by using RouterTestingModule instead of RouterModule to provide our routes. This uses a spy implementation of Location which doesn't trigger a request for a new URL but does let us know the target URL which we can use in our test specs.

How to detect Route changes in Angular?

In order to detect route change at any moment in AngularJS, this can be achieved by using the $on() method.


2 Answers

You need to create a simple Router stub to allow emitting various router events during the test.

Router stub:

// mocked source of events
const routerEventsSubject = new Subject<RouterEvent>();

const routerStub = {
    events: routerEventsSubject.asObservable()
};

describe('FooComponent', () => {
    ...

Then you simply use that stub:

...
let router: Router;

beforeEach(() => {
    TestBed.configureTestingModule({
        providers: [
            {
                provide: Router,
                useValue: routerStub
            }
        ]
    });
    router = TestBed.inject(Router);
...

A test looks like this:

it('should be loading on a navigation start', () => {
    // create a navigation event
    routerEventsSubject.next(new NavigationStart(1, 'start'));

    expect(component.isLoading).toBeTruthy();
});
like image 103
Yuri Avatar answered Oct 20 '22 00:10

Yuri


I was in a similar situation, and this is how I figured it out:

describe('router.events', () => {
  const mockStart = of(new NavigationStart(0, '/testUrl'));
  const mockEnd = of(new NavigationEnd(0, '/testUrl', '/testUrlRedirect'));
  const mockCancel = of(new NavigationCancel(0, '/testUrl', '/testReason'));
  const mockError = of(new NavigationError(0, '/testUrl', 'test error'));

  let routerEventSpy: jasmine.Spy;
  beforeEach(() => {
    routerEventSpy = spyOn(component.router, 'events');
  });

  it('should set isLoading to true', () => {
    // Arrange
    routerEventSpy.and.returnValue(mockStart);
    component.isLoading = false; // initial value to make sure code is effective

    // Act
    // Call whatever method contains your router.events.subscribe - for example:
    component.ngOnInit(); // <-- Just an example, you should replace this with your corresponding method

    // Assert
    expect(component.isLoading).toBe(true);
  });

  it('should set isLoading to false for NavigationEnd', () => {
    // Arrange
    routerEventSpy.and.returnValue(mockEnd);
    component.isLoading = true; // initial value, see first test

    // Act
    // Call whatever method contains your router.events.subscribe - for example:
    component.ngOnInit(); // <-- Just an example, see first test

    // Assert
    expect(component.isLoading).toBe(false);
  });

  it('should set isLoading to false for NavigationCancel', () => {
    // Arrange
    routerEventSpy.and.returnValue(mockCancel);
    component.isLoading = true; // initial value, see first test

    // Act
    // Call whatever method contains your router.events.subscribe - for example:
    component.ngOnInit(); // <-- Just an example, see first test

    // Assert
    expect(component.isLoading).toBe(false);
  });

  it('should set isLoading to false for NavigationError', () => {
    // Arrange
    routerEventSpy.and.returnValue(mockError);
    component.isLoading = true; // initial value, see first test

    // Act
    // Call whatever method contains your router.events.subscribe - for example:
    component.ngOnInit(); // <-- Just an example, see first test

    // Assert
    expect(component.isLoading).toBe(false);
  });
});

A few notes:

  • component should be replaced with whatever you store the fixture.componentInstance in. It is the this in your tests.
  • This would still work with a router like you have stubbed above, but it isn't using the Observable and its methods that you have declared - the spy is returning of observables to trigger the subscribe. There's likely a way to use that Router Stub for your purposes (e.g. @Yuri's answer), but your question only asked for a way to test that logic and the isLoading variable.
  • If your router is protected or private (a good practice), you can still spy on it with bracket notation - e.g. spyOn(component['router'], 'events').
like image 23
LHM Avatar answered Oct 20 '22 00:10

LHM