How to mock NavParams in tests?
Asked Answered
S

4

10

This might be an Ionic 2 only question, as I don't see NavParams in the Angular 2 docs, but some concepts might translate so I tagged both.

Given that I call navparams.get('somekey') in order to listen to parameters that are passed in, it's tricky to mock the NavParams in tests.

For example, here's how I currently do it:

export class NavParamsMock {
  public get(key): any {
    return String(key) + 'Output';
  }
}

This works for really basic tests, but what if I had a component that I have to test that it gets a specific type of Object, eg a User.

Then, I can do something like

export class NavParamsMock {
  public get(key): any {
    if (key === 'user') {
       return new User({'name':'Bob'})
    }
    return String(key) + 'Output';
  }
}

But, this doesn't work if you want to use the get(user) in another test, or even another component's spec. Say you use NavParams in 2 different components, and they both expect different result when you do get(user), it becomes increasingly tricky to mock.

Has anyone found a solution to this scenario?

Supra answered 2/1, 2017 at 20:36 Comment(0)
T
14

You can get value of your choice by implementing your own setter method.

export class NavParamsMock {
  static returnParam = null;
  public get(key): any {
    if (NavParamsMock.returnParam) {
       return NavParamsMock.returnParam
    }
    return 'default';
  }
  static setParams(value){
    NavParamsMock.returnParam = value;
  }
}

Then in each test you can access the service and set your own params object.

beforeEach(() => {
  NavParamsMock.setParams(ownParams); //set your own params here
  TestBed.configureTestingModule({
    providers: [
      {provide: NavParams, useClass: NavParamsMock},
    ]
  });
})
Tedium answered 2/1, 2017 at 20:57 Comment(2)
I'm getting a "Property returnParams does not exist on type 'NavParamsMock'... Nevermind you missed an s after the returnParams.Supra
there was a typo. returnParamTedium
J
8

Rather than mocking out the class, it's easiest to just create an instance of the NavParams class, then use it. NavParams makes the data property publicly assignable, so it can be modified in each test as needed.

The below example assumes your page looks something like this:

@IonicPage()
@Component({...})
export class YourPage {
  private data: string;

  constructor(navParams: NavParams) {
    this.data = navParams.get('data');
  }
}

I.e., you call navParams.get() in your page constructor, ionViewDidLoad(), ngOnInit(), or similar initializer function. In this case, to modify the NavParams data and ensure it's used properly, you need to modify your test injected navParams.data property, then regenerate your page:

import {IonicModule, NavParams} from 'ionic-angular';
import {ComponentFixture, TestBed} from '@angular/core/testing';

describe('YourPage', () => {
  let fixture: ComponentFixture<YourPage>;
  let component: YourPage;
  const data = {data: 'foo'};
  const navParams = new NavParams(data);

  function generateFixture() {
    fixture = TestBed.createComponent(YourPage);
    component = fixture.componentInstance;
    fixture.detectChanges();
  }

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [YourPage],
      imports: [
        IonicModule.forRoot(YourPage),
      ],
      providers: [
        {provide: NavParams, useValue: navParams},
      ]
    });
    generateFixture();
  });

  describe('NavParams', () => {
    it('should use injected data', () => {
      expect(component['data']).toEqual('foo');
    });
    it('should use new injected data', () => {
      const newData = {data: 'bar'};
      navParams.data = newData;
      generateFixture();
      expect(component['data']).toEqual('bar');
    });
  });
});

If your page calls navParams.get('key') everywhere instead of assigning to a private member, then simply reassigning the navParams.data property is sufficient in each test (no need to call generateFixture() each time).

Jar answered 3/11, 2017 at 15:26 Comment(2)
This doesn't work if I have 20 specs requiring different nav params. I'd like to dynamically set nav params inside each spec to suit each test case.Supra
The data property on NavParams is public and can be set in each test if desired. Assuming you access members in data in NavParams in the constructor of your page, you'll need to recreate your page in order to test the new data was used. But if you always access the data in NavParams directly without assigning to local members, then simply reassigning the data property of NavParams is all that is required. I've updated my answer to match.Jar
S
4

I modified @raj's answer with my own variation of this technique. @raj's only allow you to set one parameter. Mine allows for key value storage with multiple parameters.

export class NavParamsMock {
  static returnParams: any = {};

  public get(key): any {
    if (NavParamsMock.returnParams[key]) {
       return NavParamsMock.returnParams[key];
    }
    return 'No Params of ' + key + ' was supplied. Use NavParamsMock.setParams('+ key + ',value) to set it.';
  }

  static setParams(key,value){
    NavParamsMock.returnParams[key] = value;
  }
}
Supra answered 3/1, 2017 at 19:42 Comment(3)
The Property data is missing.This is how it's done: #44658548Silver
@nottinhill is this some ionic 3 change? So with my implementation above, I need to extends NavParams ?Supra
Yes you can extend, but I mock it all out, gives me full control.Silver
M
2

Here is an example with multiple params

NavParamsMock

export class NavParamsMock {

  static returnParams: any = {}

  public get (key): any {
    if (NavParamsMock.returnParams[key]) {
      return NavParamsMock.returnParams[key]
    }
  }

  static setParams (key, value): any {
    NavParamsMock.returnParams[key] = value
  }

}

Add to TestBed providers the following

{provide: NavParams, useClass: NavParamsMock}

Unit test

it('i am a unit test', () => {
    const navParams = fixture.debugElement.injector.get(NavParams)

    navParams.get =
      jasmine
        .createSpy('get')
        .and
        .callFake((param) => {
          const params = {
            'param1': 'value',
            'param2':  'value'
          }
          return params[param]
        })


    comp.ionViewDidLoad()
  })
Malcolm answered 23/2, 2018 at 11:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.