help-me-mom / ng-mocks

Angular testing library for mocking components, directives, pipes, services and facilitating TestBed setup
https://www.npmjs.com/package/ng-mocks
MIT License
1.06k stars 77 forks source link

Keeping the MatCheckboxModule with the MockBuilder #222

Closed michael-hein closed 3 years ago

michael-hein commented 4 years ago

Hi,

I ran into an issue when I wanted to keep the MatCheckboxModule with the MockBuilder. When I use this

  beforeEach(() =>
    MockBuilder(AppComponent, AppModule).keep(MatCheckboxModule)
  );

then it fails with

// ng-mocks 10.5.1
TypeError: Cannot read property 'subscribe' of undefined
            at http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/observers.js:151:58
            at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
            at ProxyZoneSpec.push.QpwO.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
            at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:363:1)
            at Zone.run (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:123:1)
            at NgZone.runOutsideAngular (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:27422:1)
            at CdkObserveContent._subscribe (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/observers.js:149:1)
            at CdkObserveContent.ngAfterContentInit (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/observers.js:136:1)
            at callHook (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:3281:1)

when I use it without keeping the MatCheckboxModule the it works

  beforeEach(() =>
    MockBuilder(AppComponent, AppModule)
  );

using without the MockBuilder works too

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [AppComponent],
      imports: [MatCheckboxModule],
    }).compileComponents();
  });

Is there a way to keep "MatCheckboxModule" with the MockBuilder without failing?

It worked with ng-mocks v10.3.0 and starting to fail with 10.4.0 but the error was different

//  ng-mocks v10.4.0
NullInjectorError: R3InjectorError(DynamicTestModule)[MatCommonModule -> HighContrastModeDetector -> HighContrastModeDetector]:
          NullInjectorError: No provider for HighContrastModeDetector!
        error properties: Object({ ngTempTokenPath: null, ngTokenPath: [ 'MatCommonModule', 'HighContrastModeDetector', 'HighContrastModeDetector' ] })
        NullInjectorError: R3InjectorError(DynamicTestModule)[MatCommonModule -> HighContrastModeDetector -> HighContrastModeDetector]:
          NullInjectorError: No provider for HighContrastModeDetector!
            at NullInjector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:1013:1)
            at R3Injector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11130:1)
            at R3Injector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11130:1)
            at injectInjectorOnly (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:899:1)
            at Module.ɵɵinject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:903:1)
            at Object.MatCommonModule_Factory [as factory] (http://localhost:9876/_karma_webpack_/node_modules/@angular/material/__ivy_ngcc__/fesm2015/core.js:174:146)
            at R3Injector.hydrate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11298:1)
            at R3Injector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11119:1)
            at http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:11156:1

I also created a repo for this one https://github.com/michael-hein/ng-mocks-mat-checkbox

Thank you :)

satanTime commented 4 years ago

Hi @michael-hein, thank you for the report. A nice finding. I'll fix it soon and release a fix.

satanTime commented 4 years ago

I think if you install 1.5.0 it should work.

michael-hein commented 4 years ago

hi @satanTime thanks for your quick reply. I tried it with ng-mocks 10.5.0 and 10.5.1 and still getting the same error.

TypeError: Cannot read property 'subscribe' of undefined
            at http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/observers.js:151:58
            at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1)
            at ProxyZoneSpec.push.QpwO.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1)
            at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:363:1)
            at Zone.run (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:123:1)
            at NgZone.runOutsideAngular (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:27422:1)
            at CdkObserveContent._subscribe (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/observers.js:149:1)
            at CdkObserveContent.ngAfterContentInit (http://localhost:9876/_karma_webpack_/node_modules/@angular/cdk/__ivy_ngcc__/fesm2015/observers.js:136:1)
            at callHook (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:3281:1)
satanTime commented 4 years ago

Thanks for the update, I found the issue and fixed it.

I'll update documentation tomorrow, and in the evening or the following day, release a new version.

satanTime commented 3 years ago

Hi @michael-hein,

might you verify that the fix works for your project? ng-mocks.zip

Also, might I ask you why you want to keep MatCheckboxModule in a test?

michael-hein commented 3 years ago

Hi @satanTime,

thank you for the fix. In the test-project it works pretty well but for the real one I run into some other errors related to other Angular-Material Components which were keept in the test.

Why we want to keep them was just the reason we wanted to test when I'm clicking on a checkbox and afterwards the save button that this save action gets dispatched with the right values. So that the formbinding, disabled states and so on works. And I think when we mocked the mat-checkbox away then it was not so easy to change the value from the ui over the formbinding. So to keep it, was the easiest way. In the evening I will check it out if its maybe the better/easier way to use the Component Harness (https://material.angular.io/guide/using-component-harnesses) to interact with the angular-material components. Currently I don't know if it works with a mocked component. If you know a better way how to deal with the Angular Material Components in the tests, I'm glad about any advice :)

One of these errors is when keeping the MatCheckboxModule and the Module has multiple Angular-Material-Modules imported, then the error MatRipple is part of the declarations of 2 modules: MatRippleModule and MockOfMatRippleModule occurs. But it only appears when providing the 'itsModuleToMock' parameter in the MockBuilder. (https://github.com/michael-hein/ng-mocks-mat-checkbox/tree/mock-of-mock)

MockBuilder(AppComponent, AppModule).keep(MatCheckboxModule)

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MatCheckboxModule,
    MatFormFieldModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
Error: Type MatRipple is part of the declarations of 2 modules: MatRippleModule and MockOfMatRippleModule! Please consider moving MatRipple to a higher module that imports MatRippleModule and MockOfMatRippleModule. You can also create a new NgModule that exports and includes MatRipple then import that NgModule in MatRippleModule and MockOfMatRippleModule.
            at verifySemanticsOfNgModuleDef (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:25958:1)
            at http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:25932:1
            at <Jasmine>
            at verifySemanticsOfNgModuleDef (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:25930:1)
            at http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:25949:1
            at <Jasmine>
            at verifySemanticsOfNgModuleDef (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:25947:1)
            at http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:25932:1
            at <Jasmine>

Thanks for your help :)

satanTime commented 3 years ago

Ah, about the declaration thing - my bad, it is exportAll flag I added last release, I'll disable it by default in the fix for this ticket.

About the test, the scenario sounds more like an integration testing, and here ngMocks cannot do much, only to mock other dependencies :)

satanTime commented 3 years ago

About the issue with

Type MatRipple is part of the declarations of 2 modules

The problem here is that MatCheckboxModule and MatFormFieldModule import the same modules, but for the check we want to keep them, whereas for the form we want to mock them due to rules of AppModule.

I think the right way would be to respect keep over mock. Because mock is mostly about declarations of the module itself, not its imports. So if I declare a component, and import an module that exports another component, when I mock my module - first of all I expect my component to be mocked, and if something causes the imported module to stay as it is - okay, I don't see how it could affect the mocked component.

But if my component depends on the imported one and I want to keep my component, then 100% the imported one I want to keep until I mock it explicitly.

Please let me know what you think about it. Perhaps you have some other cases / ideas in your head.

Thanks.

satanTime commented 3 years ago

Good news, at least for this issue I found a solution and it doesn't throw the error anymore.

satanTime commented 3 years ago

Hi again, @michael-hein, might you check the new fix? ng-mocks.zip

Please let me know how it works for you.

michael-hein commented 3 years ago

I'm glad to hear that :)

At first sight I would see it like you, respect keep over mock. Sorry, but I had some troubles to follow your cases in my mind. But I think as long as the user has the possibility to explicit mock sth. and the explicit mock then wins, then it should be fine.

With the new fix the

Type MatRipple is part of the declarations of 2 modules

error is fixed.

Sadly when I'm using MatSlideToggleModule or MatSliderModule like the same way as the MatCheckboxModule, so keeping it in the test. Then it leads to the "subscribe" error again. But only when I'm using multiple imports (MatCheckboxModule, MatSlideToggleModule) in the module. When removing MatCheckboxModule from the module then it works with the MatSlideToggleModule (https://github.com/michael-hein/ng-mocks-mat-checkbox/tree/subscribe)

  beforeEach(() =>
    MockBuilder(AppComponent, AppModule).keep(MatSlideToggleModule)
  );
@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    MatCheckboxModule, // <-- without this one it works
    MatSlideToggleModule,
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}
TypeError: Cannot read property 'subscribe' of undefined
            at MatSlideToggle.ngAfterContentInit (http://localhost:9876/_karma_webpack_/node_modules/@angular/material/__ivy_ngcc__/fesm2015/slide-toggle.js:113:1)
            at callHook (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:3281:1)
            at callHooks (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:3251:1)
            at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:3203:1)
            at refreshView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7367:1)
            at refreshComponent (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:8473:1)
            at refreshChildComponents (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7132:1)
            at refreshView (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7376:1)
            at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:7440:1)
satanTime commented 3 years ago

Thanks for the feedback, the implementation allows to change things inside of mocked / kept parent modules, let's see if it was sufficient.

Checking the new case, if you meet any other - just post it here. Looks like we are on a straight road to cover all Mat issues.

satanTime commented 3 years ago

For the current code you can use

beforeEach(() =>
  MockBuilder(AppComponent, AppModule).keep(MatSlideToggleModule).keep(FocusMonitor)
);

checking why FocusMonitor is mocked despite the kept module.

satanTime commented 3 years ago

Here we go again, ng-mocks.zip

Looking forward to hearing a new issue from you :)

michael-hein commented 3 years ago

Hi @satanTime,

with this fix, I couldn't find any new issues :)

thank you for your time and your fixes. I really appreciate that 👍

satanTime commented 3 years ago

Glad to hear. Thanks for the reports and research. Feel free to report more if you find uncovered topics and things for the enhancements.

I'll release the fixes today.

satanTime commented 3 years ago

10.5.2 has been released and contains a fix for the issue. Feel free to reopen the issue or to submit a new one if you meet any problems.