shallow-render icon indicating copy to clipboard operation
shallow-render copied to clipboard

Directives cannot be mocked anymore

Open floisloading opened this issue 2 years ago • 1 comments

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);
  });

floisloading avatar Sep 15 '22 15:09 floisloading

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.

mfrey-WELL avatar Sep 16 '22 08:09 mfrey-WELL

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

jacek-dargiel avatar Oct 31 '22 11:10 jacek-dargiel

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

jacek-dargiel avatar Oct 31 '22 11:10 jacek-dargiel

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

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

jacek-dargiel avatar Nov 03 '22 15:11 jacek-dargiel

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

floisloading avatar Nov 04 '22 10:11 floisloading

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.

FrEaKmAn avatar Nov 11 '22 10:11 FrEaKmAn

@FrEaKmAn didn't work for me either :(

floisloading avatar Nov 14 '22 11:11 floisloading

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?

sambernet avatar Dec 13 '22 13:12 sambernet

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.

jebner avatar Dec 15 '22 06:12 jebner

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.

getsaf avatar Dec 18 '22 23:12 getsaf

brandon, thank you!!! this is huge!

johnwest80 avatar Dec 19 '22 13:12 johnwest80

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

sambernet avatar Dec 20 '22 07:12 sambernet