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

9.0.1 causing issues with imports of deeply nested modules #156

Closed tashoecraft closed 4 years ago

tashoecraft commented 4 years ago

I'm not sure how this is possible based on the diff check of 9.0.0 -> 9.0.1 but I'm getting a whole bunch of

Component "x" is not part of any NgModule or the module has not been imported into your module.

The only change is me updating shallow-render one patch version. If I can put together a update I will. If I try to directly declare the component I get errors about how I'm importing it twice as MockXComponent and XComponent. So it's obviously doing it's job, but there's something going on.

Running the latest angular packages, 9.0.7, devkit 0.900.7, typescript 3.8.3. I backed down every package after an ng update, and the only one that causes the issues is shallow-render. These are modules that are importing other libraries (I'm in a NX mono repo so a decent sized dep tree). Not really sure there is anything actionable I can give you, just to put it on your radar.

Thanks for the great package, saves so many headaches!

getsaf commented 4 years ago

Ok, I'll investigate. It's been a while but I think i have seen these issues before when running tests in AOT build mode because a bunch of extra checks are done to ensure sanity of the code structure.

Just to make sure, I'm gonna download the NPM packages directly and see if something is amiss.

getsaf commented 4 years ago

I definitely don't see any differences in shallow that would cause this issue. The diff is quite small.

shallow-render does currently have a dependency on ng-mocks and I noticed that they updated their package about 4 days ago. Not saying that's the issue but it's worth checking.

Can you see if your ng-mocks dependency got updated in the process of updating shallow-render?

If so, could you post the version differences in this issue, maybe I can facilitate the conversation with them if it turns out to be something in there.

getsaf commented 4 years ago

For reference, here's the diff in the npm packages between shallow-rendere 9.0.0 - 9.0.1

diff -r shallow-9.0/dist/lib/models/renderer.js shallow-9.0.1/dist/lib/models/renderer.js
72d71
< var mock_of_provider_1 = require("./mock-of-provider");
172,173c171,181
<                                 var MockProvider = mock_of_provider_1.mockProviderClass(thingToMock, mock);
<                                 testing_1.TestBed.overrideProvider(thingToMock, { useValue: new MockProvider() });
---
>                                 if (thingToMock instanceof core_1.InjectionToken) {
>                                     testing_1.TestBed.overrideProvider(thingToMock, { useValue: mock });
>                                 }
>                                 else {
>                                     var provider = mock_provider_1.mockProvider(thingToMock, _this._setup);
>                                     testing_1.TestBed.overrideProvider(thingToMock, {
>                                         useValue: provider.useValue,
>                                         useFactory: provider.useFactory,
>                                         deps: provider.deps
>                                     });
>                                 }
diff -r shallow-9.0/dist/lib/models/rendering.d.ts shallow-9.0.1/dist/lib/models/rendering.d.ts
1,2c1,2
< import { DebugElement, EventEmitter, Type } from '@angular/core';
< import { ComponentFixture, TestBed } from '@angular/core/testing';
---
> import { DebugElement, EventEmitter, Type, InjectionToken, AbstractType } from '@angular/core';
> import { ComponentFixture } from '@angular/core/testing';
28,29c28,32
<     readonly get: <TClass>(queryClass: Type<TClass>) => TClass;
<     readonly inject: typeof TestBed['inject'];
---
>     readonly get: <TValue>(queryClass: Type<TValue> | InjectionToken<TValue> | AbstractType<TValue>) => TValue;
>     readonly inject: {
>         <T>(token: Type<T> | InjectionToken<T> | AbstractType<T>, notFoundValue?: T | undefined, flags?: import("@angular/core").InjectFlags | undefined): T;
>         <T_1>(token: Type<T_1> | InjectionToken<T_1> | AbstractType<T_1>, notFoundValue: null, flags?: import("@angular/core").InjectFlags | undefined): T_1 | null;
>     };
diff -r shallow-9.0/dist/lib/models/rendering.js shallow-9.0.1/dist/lib/models/rendering.js
52,58c52,53
<         this.get = function (queryClass) { return testing_1.TestBed.inject(queryClass); };
<         this.inject = function () {
<             var args = [];
<             for (var _i = 0; _i < arguments.length; _i++) {
<                 args[_i] = arguments[_i];
<             }
<             return testing_1.TestBed.inject.apply(testing_1.TestBed, args);
---
>         this.get = function (queryClass) {
>             return testing_1.TestBed.inject(queryClass);
59a55
>         this.inject = testing_1.TestBed.inject.bind(testing_1.TestBed);
diff -r shallow-9.0/dist/lib/shallow.d.ts shallow-9.0.1/dist/lib/shallow.d.ts
28c28
<     mock<TMock>(mockClass: Type<TMock> | InjectionToken<TMock>, stubs: RecursivePartial<TMock>): this;
---
>     mock<TMock>(thingToMock: Type<TMock> | InjectionToken<TMock>, stubs: RecursivePartial<TMock>): this;
diff -r shallow-9.0/dist/lib/shallow.js shallow-9.0.1/dist/lib/shallow.js
172,174c172,179
<     Shallow.prototype.mock = function (mockClass, stubs) {
<         var mock = this.setup.mocks.get(mockClass) || {};
<         this.setup.mocks.set(mockClass, __assign(__assign({}, mock), stubs));
---
>     Shallow.prototype.mock = function (thingToMock, stubs) {
>         var mock = this.setup.mocks.get(thingToMock);
>         if (typeof mock === 'object') {
>             this.setup.mocks.set(thingToMock, __assign(__assign({}, mock), stubs));
>         }
>         else {
>             this.setup.mocks.set(thingToMock, stubs);
>         }
diff -r shallow-9.0/dist/lib/tools/mock-provider.js shallow-9.0.1/dist/lib/tools/mock-provider.js
31a32,34
>     if (provide instanceof core_1.InjectionToken) {
>         return userMocks ? { provide: provide, useValue: userMocks } : provider;
>     }
hmil commented 4 years ago

Same thing here. I get the error on 9.0.1 but not on 9.0.0.

getsaf commented 4 years ago

I just tried to reproduce the issue in this Angular 9 project without success. I pushed the project up to this repo: https://github.com/getsaf/ng9-shallow-test

If it's not too much trouble could you give me the exact error message you're seeing (without the X substitutions and with any stack traces you may see)?

Also, (big ask), could you reproduce the issue in that project? I'm struggling with ideas on how to get it to happen.

hmil commented 4 years ago

Unfortunately my project is a bit of a mess right now and I was not able to extract a minimal repro.

However, I dissected the diff and I can tell you that this is what is causing the problem:

if (provide instanceof core_1.InjectionToken) {
    return userMocks ? { provide: provide, useValue: userMocks } : provider;
}

The bug vanishes when I surgically remove this bit of code from my local node_modules.

The best I can offer in terms of repro is a WiP branch of a project I am currently working on. Clone the branch shallow-render-bug, then run make from the top and then cd packages/vaultage-pwa; yarn test.

getsaf commented 4 years ago

That's awesome info 👍. I'll look more into it this weekend and hopefully come up with a reproduction and a fix.

Thanks for helping!

hmil commented 4 years ago

More details: Of all the providers which can enter this branch, the one which seems to trigger the bug is AnalyzeForEntryComponents. This provider is added automatically by angular to the routing module via the call to Router.forRoot(routes). Unsurprisingly, the components which show up in the error message are those specified in the routes...

Still not able to get a clean repro though :/

getsaf commented 4 years ago

While I was unable to reproduce the issue on my end, I think I found the issue on the line of code you pointed me to in your previous comment (thanks again). It looks like that code was causing "real" injection tokens to be used by default which seems to be a bad move on my part.

I added some tests to specifically cover the original behavior of mocking InjectionTokens automatically (while maintaining the feature that allows mocking InjectionTokens in tests).

I published v9.0.2, could you please install this one and see that it fixes your issue?

tashoecraft commented 4 years ago

I updated to 9.0.2 but am still experiencing the issue.

getsaf commented 4 years ago

Do you have a stack trace I could look at. Also, is there any way I could have a look at the full test file for a test that has this issue, this would be super-helpful in diagnosing on my end.

Thanks in advance.

tashoecraft commented 4 years ago

So I was able to recreate with a public repo: https://github.com/tashoecraft/nx-examples after install run: ng test products-home-page The problem seems to be with this test:

  it('should create', async () => {
    expect(shallow).toBeTruthy();
    rendered = await shallow.render();
    expect(rendered).toBeTruthy();
  });

shallow is truthy, but it errors when inside the async function. I'll try and dig deeper

hmil commented 4 years ago

9.0.2 is also not fixing the issue for me. Same error as before.

getsaf commented 4 years ago

Oh wow, so it turns out I accidentally closed the PR for the fix instead of merging it so when I published 9.0.2, it was identical to 9.0.1 🤦‍♂. I'm gonna blame this on all the recent coronavirus stress.

Good news is that 9.0.3 definitely fixes the issue. Thanks to @tashoecraft for the repo. I verified the issue and fix.

image

Thanks again for your help in debugging!

tashoecraft commented 4 years ago

@getsaf All my tests pass now, great work and thanks for the help!