19

I am trying to write unit test for getCurrentNavigation().extras.state using jasmine.

To solve the issue, I have tried to spy this router method.

My component file,

@Component({
  selector: 'app-location-list',
  templateUrl: './location-list.component.html',
  styleUrls: ['./location-list.component.scss']
})
export class LocationListComponent implements OnInit{

  locationId;
  locationName;
  constructor(private activatedRoute: ActivatedRoute, private router: Router) {
    if (this.router.getCurrentNavigation().extras.state) {
      this.locationId = this.router.getCurrentNavigation().extras.state.locationId;
    }
    if (this.router.getCurrentNavigation().extras.state) {
      this.locationName = this.router.getCurrentNavigation().extras.state.locationName;
    }
  }
  ngOnInit() { }
}

My Spec file,

describe('LocationListComponent ', () => {
  let component: LocationListComponent ;
  let fixture: ComponentFixture<LocationListComponent >;

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

  beforeEach(() => {
    fixture = TestBed.createComponent(LocationListComponent );
    component = fixture.componentInstance;
    spyOn(Router.prototype, 'getCurrentNavigation').and.returnValues({ 'extras': { 'state': { 'locationId': 100, 'locationName': "UK" } } });
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});

But I'm getting the following error,

TypeError: Cannot read property 'extras' of null

Can anyone help me to resolve this issue. I'm using Angular 7.2

Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
Arun G
  • 254
  • 1
  • 4
  • 10

4 Answers4

20

If we try to use both RouterTestingModule and {provide: Router, useClass: RouterStub} it will throw error of cannot read property 'root' of undefined

So we can directly create spy for Route and return value of it

describe('LocationListComponent', () => {
  let component: LocationListComponent ;
  let fixture: ComponentFixture<LocationListComponent>;
  let router: jasmine.SpyObj<Router>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        LocationListComponent 
      ],
    })
      .compileComponents();
  }));

  beforeEach(() => {
    router = TestBed.get(Router);
    spyOn(router, 'getCurrentNavigation').and.returnValue({ extras: { state: { message: 'msg'} } } as any);
    fixture = TestBed.createComponent(LocationListComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
Bhavin
  • 970
  • 1
  • 13
  • 20
  • 1
    I like this approach, but on the `spyOn` line it says that the argument `is not assignable to parameter of type 'Navigation': is missing the following properties from type 'Navigation': id, initialUrl, extractedUrl, trigger, previousNavigation`. – A.Wan Aug 13 '20 at 17:28
  • 1
    @A.Wan you can put "as any" after the object, so it will be accepted. I think this is common way to mock partial objects in tests. – Boat Aug 17 '20 at 12:00
  • 1
    @A.Wan thanks for the issue, I have updated the answer – Bhavin Aug 22 '20 at 04:36
  • In my case, it does not throw the such error when I solve all issues. – cakePHP Aug 12 '21 at 12:34
  • Thanks for this! You can (maybe should) use `as SpyObj` vs `as any` – Red2678 Sep 06 '21 at 20:37
  • Hint for newer Angular versions: Use `TestBed.inject(Router);` instead of `TestBed.get(Router);` – iceteabottle Mar 01 '23 at 12:46
15

You can easily do it using stub & useClass which can be reused at other spec files as well if you can create it in separate file and export class RouterStub , try:

In spec file create a stub which will have same method as Router:

class RouterStub{
 getCurrentNavigation(){
   return {
      extras: {
         state:{
           locationId: 'someId',
           locationName: 'someName'
         }
       }
     }
   }
}

and in beforeEach() block:

describe('LocationListComponent ', () => {
  let component: LocationListComponent ;
  let fixture: ComponentFixture<LocationListComponent >;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule
      ],
      declarations: [
        LocationListComponent 
      ],
      providers: [ {provide: Router, useClass: RouterStub}]
    })
      .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LocationListComponent );
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });
});
Shashank Vivek
  • 16,888
  • 8
  • 62
  • 104
  • With your approach will be undefined getCurrentNavigation() – Carnaru Valentin Dec 12 '19 at 09:36
  • How do you test it with different properties? – Thaadikkaaran Jun 11 '20 at 12:59
  • You need a spy on the router's `getCurrentNavigation` method in your `beforeEach` - otherwise, you will get an error of `cannot read property 'root' of undefined`. See [this post](https://newbedev.com/how-to-do-unit-testing-for-getcurrentnavigation-extras-state-in-angular-7) – Johan Aspeling Jul 10 '21 at 17:34
  • Now suppose this page having ```this.router.navigate(['page-name'], navigationExtras)``` then how this will work as this need something like this https://stackoverflow.com/a/40301110 where we can not write two time same provider. – cakePHP Aug 12 '21 at 09:50
1

Unit testing for router.getCurrentNavigation() can be done in 3 easy steps:

  1. Create mock router object:

    const mockRouter = {
      getCurrentNavigation: jasmine.createSpy('getCurrentNavigation')
    };
    
  2. Inject this in providers array:

    providers: [{ provide: Router, useValue: mockRouter }]
    
  3. Return the desired mocked value in the beforeEach() function

    mockRouter.getCurrentNavigation.and.returnValue({
      extras: {
        state: {
          test: ''
        }
      }
    });
    
Aakash Goplani
  • 1,150
  • 1
  • 19
  • 36
-2

You need to get the router instance from fixture

router = fixture.debugElement.injector.get(Router);
Siddhartha Gupta
  • 1,140
  • 8
  • 13