Error: No provider for DeepLinker while unit testing components


#1

Testing suite is still not ready in ionic 2. So I am using this tutorial for the test. This essentially uses angular 2 testing suite . Here is my test.ts

declare var __karma__: any;
declare var require: any;

// Prevent Karma from running prematurely.
__karma__.loaded = function (): void { /* no op */};

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
  BrowserDynamicTestingModule,
  platformBrowserDynamicTesting(),
);
// Then we find all the tests.
const context: any = require.context('./', true, /\.spec\.ts/);
// And load the modules.
context.keys().map(context);
// Finally, start Karma to run the tests.
__karma__.start();

export class TestUtils {

  public static beforeEachCompiler(components: Array<any>): Promise<{fixture: any, instance: any}> {
    return TestUtils.configureIonicTestingModule(components)
    .compileComponents().then(() => {
      let fixture: any = TestBed.createComponent(components[0]);
      return {
        fixture: fixture,
        instance: fixture.debugElement.componentInstance,
      };
    });
  }

  public static configureIonicTestingModule(components: Array<any>): typeof TestBed {
    return TestBed.configureTestingModule({
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
      declarations: [
      ...components
      ],
      providers: [
      NavController,LoadingController,App, Form, Keyboard, DomController, MenuController,
      { provide: Bookemon, useClass: BookMock},
      { provide: Authentication, useClass: AuthMock},
      {provide: Storage, useClass: StorageMock},
      {provide: Config, useClass: ConfigMock},
      {provide: Platform, useClass: PlatformMock},
      {provide: Events, useClass: Eventsmock}
      ],
      imports: [
      FormsModule,
      IonicModule,
      ReactiveFormsModule,
      SwingModule
      ],
    });
  }

I am trying to test my tab.ts component.

tab.ts

@Component({
  selector: 'page-tabs',
  templateUrl: 'tabs.html'
})
export class TabsPage {
  user: any;
  tab1Root: any = UserHuntsPage;
  tab2Root: any = HomePage;
  tab3Root: any = FeedPage;

  constructor(public navCtrl: NavController , public auth: Authentication ,public events: Events) {

  }
  ngOnInit(){
    this.events.subscribe('logout-user' , () => {
      this.navCtrl.setRoot(WelcomePage);
     })
  }
}

Here is tab.pec.ts

let fixture: ComponentFixture<TabsPage> = null;
let instance: any = null;
describe('Tabs component',()=>{
  beforeEach(async(() => TestUtils.beforeEachCompiler([TabsPage]).then(compiled => {
    fixture = compiled.fixture;
    instance = compiled.instance;
  })));
  it('initialises', () => {
    expect(1).toBeTruthy();
  });
})

When i run the test I get this error,

Error: No provider for DeepLinker!
    at NoProviderError.BaseError [as constructor] (webpack:///~/@angular/core/src/facade/errors.js:24:0 <- src/test.ts:8542:34)
    at NoProviderError.AbstractProviderError [as constructor] (webpack:///~/@angular/core/src/di/reflective_errors.js:41:0 <- src/test.ts:72995:16)
    at new NoProviderError (webpack:///~/@angular/core/src/di/reflective_errors.js:72:0 <- src/test.ts:73026:16)
    at ReflectiveInjector_._throwOrNull (webpack:///~/@angular/core/src/di/reflective_injector.js:758:0 <- src/test.ts:119137:19)
    at ReflectiveInjector_._getByKeyDefault (webpack:///~/@angular/core/src/di/reflective_injector.js:786:0 <- src/test.ts:119165:25)
    at ReflectiveInjector_._getByKey (webpack:///~/@angular/core/src/di/reflective_injector.js:749:0 <- src/test.ts:119128:25)
    at ReflectiveInjector_.get (webpack:///~/@angular/core/src/di/reflective_injector.js:558:0 <- src/test.ts:118937:21)
    at TestBed.get (webpack:///~/@angular/core/bundles/core-testing.umd.js:814:0 <- src/test.ts:39277:67)
    at CompiledTemplate.proxyViewClass.AppView.injectorGet (webpack:///~/@angular/core/src/linker/view.js:109:0 <- src/test.ts:119679:45)
    at CompiledTemplate.proxyViewClass.DebugAppView.injectorGet (webpack:///~/@angular/core/src/linker/view.js:351:0 <- src/test.ts:119921:49)
Error: Uncaught (in promise): Error: Error in ./TabsPage class TabsPage - inline template:0:0 caused by: No provider for DeepLinker!
    at resolvePromise (webpack:///~/zone.js/dist/zone.js:468:0 <- src/test.ts:141307:31)
    at resolvePromise (webpack:///~/zone.js/dist/zone.js:453:0 <- src/test.ts:141292:17)
    at webpack:///~/zone.js/dist/zone.js:502:0 <- src/test.ts:141341:17
    at ZoneDelegate.invokeTask (webpack:///~/zone.js/dist/zone.js:265:0 <- src/test.ts:141104:35)
    at ProxyZoneSpec.onInvokeTask (webpack:///~/zone.js/dist/proxy.js:103:0 <- src/test.ts:110667:39)
    at ZoneDelegate.invokeTask (webpack:///~/zone.js/dist/zone.js:264:0 <- src/test.ts:141103:40)
    at Zone.runTask (webpack:///~/zone.js/dist/zone.js:154:0 <- src/test.ts:140993:47)
    at drainMicroTaskQueue (webpack:///~/zone.js/dist/zone.js:401:0 <- src/test.ts:141240:35)

I have not used DeepLinker anywhere in my app. I am thinking it might be a dependency of something in my providers. I have spent a lot of time on this and I cant understand why this is happening. Any help would be appreciated.

Edit

I added DeepLinkerin the providers but that gives another error,

Failed: Can't resolve all parameters for DeepLinker: (?, ?, ?).
Error: Can't resolve all parameters for DeepLinker: (?, ?, ?).
    at CompileMetadataResolver._getDependenciesMetadata (webpack:///~/@angular/compiler/src/metadata_resolver.js:623:0 <- src/test.ts:50686:19)
    at CompileMetadataResolver._getTypeMetadata (webpack:///~/@angular/compiler/src/metadata_resolver.js:517:0 <- src/test.ts:50580:26)
    at webpack:///~/@angular/compiler/src/metadata_resolver.js:667:0 <- src/test.ts:50730:41
    at Array.forEach (native)
    at CompileMetadataResolver._getProvidersMetadata (webpack:///~/@angular/compiler/src/metadata_resolver.js:647:0 <- src/test.ts:50710:19)
    at CompileMetadataResolver._loadNgModuleMetadata (webpack:///~/@angular/compiler/src/metadata_resolver.js:430:0 <- src/test.ts:50493:50)
    at CompileMetadataResolver.loadNgModuleMetadata (webpack:///~/@angular/compiler/src/metadata_resolver.js:313:0 <- src/test.ts:50376:29)
    at RuntimeCompiler._loadModules (webpack:///~/@angular/compiler/src/runtime_compiler.js:99:0 <- src/test.ts:69258:41)
    at RuntimeCompiler._compileModuleAndAllComponents (webpack:///~/@angular/compiler/src/runtime_compiler.js:83:0 <- src/test.ts:69242:35)
    at RuntimeCompiler.compileModuleAndAllComponentsAsync (webpack:///~/@angular/compiler/src/runtime_compiler.js:65:0 <- src/test.ts:69224:21)

edit

adding providers as { provide: DeepLinker, useValue: {} } results in new error,

TypeError: this.parent.registerChildNav is not a function
    at new Tabs (webpack:///~/ionic-angular/components/tabs/tabs.js:173:0 <- src/test.ts:46470:25)
    at new Wrapper_Tabs (/IonicModule/Tabs/wrapper.ngfactory.js:7:18)
    at CompiledTemplate.proxyViewClass.View_TabsPage0.createInternal (/DynamicTestModule/TabsPage/component.ngfactory.js:27:20)
    at CompiledTemplate.proxyViewClass.AppView.create (webpack:///~/@angular/core/src/linker/view.js:74:0 <- src/test.ts:119644:21)
    at CompiledTemplate.proxyViewClass.DebugAppView.create (webpack:///~/@angular/core/src/linker/view.js:330:0 <- src/test.ts:119900:44)
    at CompiledTemplate.proxyViewClass.View_TabsPage_Host0.createInternal (/DynamicTestModule/TabsPage/host.ngfactory.js:16:19)
    at CompiledTemplate.proxyViewClass.AppView.createHostView (webpack:///~/@angular/core/src/linker/view.js:81:0 <- src/test.ts:119651:21)
    at CompiledTemplate.proxyViewClass.DebugAppView.createHostView (webpack:///~/@angular/core/src/linker/view.js:341:0 <- src/test.ts:119911:52)
    at ComponentFactory.create (webpack:///~/@angular/core/src/linker/component_factory.js:154:0 <- src/test.ts:54276:25)
    at initComponent (webpack:///~/@angular/core/bundles/core-testing.umd.js:852:0 <- src/test.ts:39321:53)
Error: Uncaught (in promise): Error: Error in ./TabsPage class TabsPage - inline template:0:0 caused by: this.parent.registerChildNav is not a function
    at resolvePromise (webpack:///~/zone.js/dist/zone.js:468:0 <- src/test.ts:141416:31)
    at resolvePromise (webpack:///~/zone.js/dist/zone.js:453:0 <- src/test.ts:141401:17)
    at webpack:///~/zone.js/dist/zone.js:502:0 <- src/test.ts:141450:17
    at ZoneDelegate.invokeTask (webpack:///~/zone.js/dist/zone.js:265:0 <- src/test.ts:141213:35)
    at ProxyZoneSpec.onInvokeTask (webpack:///~/zone.js/dist/proxy.js:103:0 <- src/test.ts:110667:39)
    at ZoneDelegate.invokeTask (webpack:///~/zone.js/dist/zone.js:264:0 <- src/test.ts:141212:40)
    at Zone.runTask (webpack:///~/zone.js/dist/zone.js:154:0 <- src/test.ts:141102:47)
    at drainMicroTaskQueue (webpack:///~/zone.js/dist/zone.js:401:0 <- src/test.ts:141349:35)

#2

Yes I get the same thing. Only with tabs.ts. Been working around it with basically hard-coded stubs.


#3

Same for me here when using tabs.
Any workaround?


#4

Unit testing Ionic is giving me more white hairs than I care for. The sooner they release a mock library for their framework instead of forcing users to create them and delve into the inner workings of their product the better. It’s ridiculous that I spend more time fixing that stuff than creating and testing my own code.


#5

Your mileage may vary as to how well this will work for you, but I eventually went the route of avoiding ComponentFixture for unit tests of tabs.ts, and then either using spies with callFake or providing a hard-coded stub/mock class.

describe("The Tabs Page", () => {

  let tabsPage: TabsPageComponent;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: ToastController, useClass: ToastCtrlMock },
        { provide: Push, useClass: PushMock },
        TabsPageComponent,
      ],
    });
  });

  beforeEach(inject([TabsPageComponent], (page: TabsPageComponent) => {
    tabsPage = page;
  }));

This works for me. I just use standard spies now, call tabsPage.methodName() then expect on whatever, as in:

it("should unsubscribe from push notifications when destroyed", fakeAsync(() => {
    tabsPage.ngOnInit();
    spyOn(tabsPage.pushSubscription, "unsubscribe");
    tick();
    tabsPage.ngOnDestroy();
    expect(tabsPage.pushSubscription.unsubscribe).toHaveBeenCalled();
  }));

Hope it helps