getsaf / shallow-render

Angular testing made easy with shallow rendering and easy mocking. https://getsaf.github.io/shallow-render
MIT License
273 stars 25 forks source link

Mocking injection tokens only seems to work if the value is an object #153

Closed kevinbeal closed 4 years ago

kevinbeal commented 4 years ago

I have a Stackblitz here: https://stackblitz.com/edit/github-41xrxw

The testing library appears to assume any injected value via a token is an object. Angular itself imposes no such limitation.

I have a use case where I have defined a function with properties on it, like this:

interface IBootstrapper {
    (...options: IBootstrapOptions[]): IBootstrapResult;
    adapters: {
        onAppLoad(callback: MessengerCallback): IBootstrapOptions;
        reportAll(except?: RegExp | string[]): IBootstrapOptions;
    };
}

And I'm providing it in an Angular module like this:

    providers: [{ provide: BOOTSTRAP, useValue: bootstrap }]

Using shallow-render on a component with injected members of types other than "object" doesn't seem to work when mocking like:

    shallow.mock(BOOTSTRAP, fakeBootstrap)

In this case, the .adapters property makes it through, but it's not callable.

Providing it this way doesn't seem to work either:

    shallow.provide({provide: BOOTSTRAP, useValue: fakeBootstrap});

In this case, a mock is auto-generated and fakeBootstrap is not used.

Am I doing something wrong? Is it a bug? Something else? :)

getsaf commented 4 years ago

Injection tokens are a little weird. I think your StackBlitz shows that it's something we can be better at with in shallow-render. It would be trivial to detect when mocking an Injection token and allow non-object things to be passed through. I'll make this happen in the next release soon.

You may want to try one of these alternatives for now:

Using provideMock helps, here's a fork of your StackBlitz with the fix.

  beforeEach(() => {
    shallow = new Shallow(LabelTextComponent, LabelTextModule)
      .provideMock({provide: MY_TOKEN, useValue: example2})
      .provideMock({provide: MY_TOKEN2, useValue: 'test2'})
  });

The above is equivalent to:

shallow
  .provide({provide: MY_TOKEN, useValue: example2}).dontMock(MY_TOKEN)
  .provide({provide: MY_TOKEN2, useValue: 'test2'}).dontMock(MY_TOKEN2);

For "global" tokens like BOOTSTRAP, I'd probably do this globally with:

Shallow
  .alwaysProvide({provide: MY_TOKEN, useValue: example2}).neverMock(MY_TOKEN)
  .alwaysProvide({provide: MY_TOKEN2, useValue: 'test2'}).neverMock(MY_TOKEN2);
kevinbeal commented 4 years ago

Yay! It works :)

Thank you for the library, the help, and the fix!

getsaf commented 4 years ago

Fixed by #154