ebakunin
ebakunin

Reputation: 3791

Testing route navigation without an outlet in Angular

I am writing a spec for an Angular component that displays a button that will navigate to another page. The component makes use of Router::navigate() but does not itself have a router outlet. A parent component has the outlet. In my spec, the test should confirm that clicking on the button routes to the correct path.

My current (broken) spec tries to use RouterTestingModule to provide a route to a DummyComponent. When the button is clicked in the spec I get the following error:

'Unhandled Promise rejection:', 'Cannot find primary outlet to load 'DummyComponent'', '; Zone:', 'angular', '; Task:', 'Promise.then', '; Value:', Error{__zone_symbol__error: Error{originalStack: 'Error: Cannot find primary outlet to load 'DummyComponent'

Obviously I am approaching this problem in the wrong manner. What is the correct way to test router navigation when the component does not have a router outlet?

The component (pseudo-code):

@Component({
    template: `
        Go to the <button (click)="nextPage">next page</button>
    `
})
export class ExampleComponent {
    public myId = 5;

    constructor(private _router: Router);

    public nextPage(): void {
        this._router.navigate(['/example', this.myId]);
    }
}

The spec. This does not work:

const FAKE_ID = 999;

describe('ExampleComponent Test', () => {
    let exampleComponent: ExampleComponent;
    let fixture: ComponentFixture<ExampleComponent>;

    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [ DummyComponent ],
            imports: [
                RouterTestingModule.withRoutes([
                    { path: 'example/:id', component: DummyComponent }
                ]);
            ]
        });

        fixture = TestBed.createComponent(exampleComponent);
        exampleComponent = fixture.componentInstance;
    });

    it('should route to example/:id', inject([Router, Location], (router: Router, location: Location) => {
        fixture.detectChanges();
        exampleComponent.myId = FAKE_ID;

        const LINK_BUTTON = fixture.debugElement.query(By.css('button'));
        LINK_BUTTON.nativeElement.dispatchEvent(new Event('click'));
        expect(location.path()).toEqual('/example/' + FAKE_ID);
    });
});

Upvotes: 2

Views: 1741

Answers (1)

Paul Samsotha
Paul Samsotha

Reputation: 208964

There needs to be an outlet (<router-outlet>) for the DummyComponent. If the DummyComponent is a route being navigated to from the ExampleComponent, then the ExampleComponent should have the outlet. You also also need to add the ExampleComponent to the declarations`

@Component({
  tempalte: `
    <router-outlet></router-outlet>
    <button (click)="nextPage">next page</button>
  `
})
class ExampleComponent{}

declarations: [ ExampleComponent, DummyComponent ]

If you want to avoid having to set up this infrastructure just to test the route being navigated to, the better option might be to just mock the Router, and just check that the navigate method is called with the correct path.

beforeEach(()=>{
  TestBed.configureTestingModule({
    providers: [
      {
        provide: Router,
        useValue: { navigate: jasmine.createSpy('navigate') }
      }
    ]
  })
})

With this, you don't need to configure an routing at all, as you're using a fake Router. Then in your test

it('should route to example/:id', inject([Router], (router: Router) => {
  expect(router.navigate).toHaveBeenCalledWith(['/example', FAKE_ID]);
});

Upvotes: 1

Related Questions