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

Directives cannot be mocked anymore #225

Closed floisloading closed 1 year ago

floisloading commented 2 years ago

Since updating to Angular 14.2.0, all Shallow-specs for Components with Directives in their temlates are failing with this message:

Error: NG0202: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid. This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.

Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors. error properties: Object({ code: 202 })

Complete console output of ng test

✔ Browser application bundle generation complete.
15 09 2022 17:29:00.958:WARN [karma]: No captured browser, open http://localhost:9876/
15 09 2022 17:29:01.094:INFO [karma-server]: Karma v6.4.0 server started at http://localhost:9876/
15 09 2022 17:29:01.095:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
15 09 2022 17:29:01.098:INFO [launcher]: Starting browser Chrome
15 09 2022 17:29:03.625:INFO [Chrome 105.0.0.0 (Mac OS 10.15.7)]: Connected on socket -8NYow9oZdd_-iePAAAB with id 17397697
Chrome 105.0.0.0 (Mac OS 10.15.7) AppComponent should create FAILED
    Error: NG0202: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
    This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.

    Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.
    error properties: Object({ code: 202 })
    Error: NG0202: This constructor is not compatible with Angular Dependency Injection because its dependency at index 0 of the parameter list is invalid.
    This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
    Please check that 1) the type for the parameter at index 0 is correct and 2) the correct Angular decorators are defined for this class and its ancestors.
        at ɵɵinvalidFactoryDep (node_modules/@angular/core/fesm2020/core.mjs:4798:11)
        at NodeInjectorFactory.factory (ng:///MockOfTestDirective/ɵfac.js:4:47)
        at getNodeInjectable (node_modules/@angular/core/fesm2020/core.mjs:3523:44)
        at instantiateAllDirectives (node_modules/@angular/core/fesm2020/core.mjs:12689:27)
        at createDirectivesInstances (node_modules/@angular/core/fesm2020/core.mjs:12113:5)
        at ɵɵelementStart (node_modules/@angular/core/fesm2020/core.mjs:15243:9)
        at ɵɵelement (node_modules/@angular/core/fesm2020/core.mjs:15298:5)
        at templateFn (ng:///AppComponent.js:371:9)
        at executeTemplate (node_modules/@angular/core/fesm2020/core.mjs:12084:9)
        at renderView (node_modules/@angular/core/fesm2020/core.mjs:11906:13)
Chrome 105.0.0.0 (Mac OS 10.15.7): Executed 2 of 2 (1 FAILED) (0.107 secs / 0.098 secs)
TOTAL: 1 FAILED, 1 SUCCESS

Output of ng version

Angular CLI: 14.2.2
Node: 16.17.0
Package Manager: npm 8.19.2
OS: darwin x64

Angular: 14.2.2
... animations, cli, common, compiler, compiler-cli, core, forms
... platform-browser, platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1402.2
@angular-devkit/build-angular   14.2.2
@angular-devkit/core            14.2.2
@angular-devkit/schematics      14.2.2
@schematics/angular             14.2.2
rxjs                            7.5.6
typescript                      4.7.4

Minimal reproduction of the problem

ng new shallow-test --defaults
cd shallow-test
ng generate directive test
echo "<div appTest></div>" >> src/app/app.component.html
npm i --save-dev shallow-render

Paste this into app.component.spec.ts:

import { Shallow } from 'shallow-render';
import { AppComponent } from './app.component';
import { AppModule } from './app.module';

describe('AppComponent', () => {
  let shallow: Shallow<AppComponent>;

  beforeEach(() => {
    shallow = new Shallow(AppComponent, AppModule);
  });

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

And execute ng test

Workaround

The only solution I found is to not mock it at all:

  beforeEach(() => {
    shallow = new Shallow(AppComponent, AppModule).dontMock(TestDirective);
  });
mfrey-WELL commented 2 years ago

I have the same issue. It also happens on Angular 14.1.3. Interestingly, cloning this project, upgrading to V14.2 and running the directive example still works fine.

Thank you for the workaround.

jacek-dargiel commented 2 years ago

I have the same problem after upgrading from A12 to A13.

jacek-dargiel commented 2 years ago

Also, using .dontMock isn't a great solution. It doesn't make sense to dontMock the whole Angular Material library, routerLink, etc.

jacek-dargiel commented 2 years ago

@getsaf Is there a chance you could have a look at this?

@floisloading Did you ever find a solution to this problem?

floisloading commented 2 years ago

@jacek-dargiel unfortunately not, but I didn't try.

FrEaKmAn commented 2 years ago

Others are solving the issue by adding

    "emitDecoratorMetadata": true,
    "esModuleInterop": true,

to tsconfig.spec.json, but this doesn't solve the issue for me. It's a huge blocker on our side.

floisloading commented 1 year ago

@FrEaKmAn didn't work for me either :(

sambernet commented 1 year ago

Started running into this in our ng 14 upgrade also.

Several hundred tests failing 😱 I am afraid there is no easy bailout for us.

Did anyone already do some investigation of what the actual root cause is, and if it could be fixed in a reasonable way? Maybe we could help with that?

jebner commented 1 year ago

I looked a bit into the issue. This repo could also be used to reproduce the issue. It's a fresh angular library created according to: https://angular.io/guide/creating-libraries

According to these Angular Docs the base classes of @Directive/@Component annotated classes need the annotation as well:

"[...] When a class has a @Directive() or @Component() decorator, the Angular compiler generates extra code to inject dependencies into the constructor. When using inheritance, Ivy needs both the parent class and the child class to apply a decorator to generate the correct code."

So is it possible that the @Directive annotation is missing on the classes which are extended by MockDirective? That would be Mock and MockWithStubs . But those classes are also used by MockComponent so the @Directive() / @Component() decorators would probably have to be set dynamic / conditional.

Could anybody have a look at this? This issue is making the upgrade to angular 14 quite hard for us as we have many hundred tests using shallow-render.

getsaf commented 1 year ago

Hey all, sorry for the delay in getting this fix. Debugging was tough but the fix was pretty simple. Looks like the directive constructor props weren't being picked up by the Angular compiler so I manually added an @Inject directive.

LMK how v14.2.0 works for you.

johnwest80 commented 1 year ago

brandon, thank you!!! this is huge!

sambernet commented 1 year ago

Thanks a lot, 14.2 works perfectly fine on our end. I reckon that wasn't easy to debug 💪 👍