How to do unit testing for getCurrentNavigation().extras.state in angular 7
Asked Answered
D

4

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

Ducks answered 23/8, 2019 at 5:9 Comment(0)
M
16

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();
  });
});
Materialize answered 24/8, 2019 at 16:46 Comment(4)
With your approach will be undefined getCurrentNavigation()Nemertean
How do you test it with different properties?Quean
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 postPlantain
Now suppose this page having this.router.navigate(['page-name'], navigationExtras) then how this will work as this need something like this stackoverflow.com/a/40301110 where we can not write two time same provider.Wasson
P
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();
  });
});
Presurmise answered 23/4, 2020 at 8:45 Comment(6)
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.Speculate
@Speculate 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.Apocalyptic
@Speculate thanks for the issue, I have updated the answerPresurmise
In my case, it does not throw the such error when I solve all issues.Wasson
Thanks for this! You can (maybe should) use as SpyObj<Router> vs as anyBurletta
Hint for newer Angular versions: Use TestBed.inject(Router); instead of TestBed.get(Router);Tootle
M
16

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();
  });
});
Materialize answered 24/8, 2019 at 16:46 Comment(4)
With your approach will be undefined getCurrentNavigation()Nemertean
How do you test it with different properties?Quean
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 postPlantain
Now suppose this page having this.router.navigate(['page-name'], navigationExtras) then how this will work as this need something like this stackoverflow.com/a/40301110 where we can not write two time same provider.Wasson
O
2

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: ''
        }
      }
    });
    
Ormazd answered 2/11, 2021 at 10:40 Comment(0)
C
-2

You need to get the router instance from fixture

router = fixture.debugElement.injector.get(Router);
Cynewulf answered 23/8, 2019 at 9:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.