Skip to content

Commit

Permalink
fix(mock-builder): respecting forward-ref and modules with providers #…
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed May 6, 2021
1 parent 37584d0 commit 4a099b8
Show file tree
Hide file tree
Showing 13 changed files with 686 additions and 28 deletions.
77 changes: 77 additions & 0 deletions docs/articles/guides/libraries/ngrx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: How to test usage of ngrx in Angular applications
sidebar_label: NGRX
---

`ng-mocks` perfectly mocks `NGRX` modules. However, there are might be issues if some of them should be kept.

Besides `StoreModule`, which is an entry point to configure effects and reducers,
under the hood `NGRX` uses four other modules, and these modules should be configured in `MockBuilder`:

- `StoreRootModule`
- `StoreFeatureModule`
* `EffectsRootModule`
* `EffectsFeatureModule`

Or a simple value is to rely on `.ngModule` property:

- `StoreModule.forRoot().ngModule`
- `StoreModule.forFeature().ngModule`
* `EffectsModule.forRoot().ngModule`
* `EffectsModule.forFeature().ngModule`

Let's imagine that we want to keep `StoreModule` in a test,
then we should pass the modules into [`.keep`](../../api/MockBuilder.md#keep):

```ts
beforeEach(() =>
MockBuilder(MyComponent, MyModule)
.keep(StoreRootModule)
.keep(StoreFeatureModule)
.keep(EffectsRootModule)
.keep(EffectsFeatureModule)
);
```

or

```ts
beforeEach(() =>
MockBuilder(MyComponent, MyModule)
.keep(StoreModule.forRoot().ngModule)
.keep(StoreModule.forFeature().ngModule)
.keep(EffectsModule.forRoot().ngModule)
.keep(EffectsModule.forFeature().ngModule)
);
```

Please pay attention that `MyModule` or its imports should import `.forRoot` and `.forFeature`,
otherwise we need to call [`.keep`](../../api/MockBuilder.md#keep) as usually:

```ts
beforeEach(() =>
MockBuilder(MyComponent, MyModule)
.keep(StoreModule.forRoot())
.keep(StoreModule.forFeature())
.keep(EffectsModule.forRoot())
.keep(EffectsModule.forFeature())
);
```

## Lazy loaded modules with `.forFeature()`

When we want to test a lazy loaded module, it means there is no import of `.forRoot()` calls,
because they are in a parent module.
Therefore, the `.forRoot()` calls should be added manually without any special configuration:

```ts
beforeEach(() =>
MockBuilder(SomeComponent, LazyLoadedModule)
// providing root tools
.keep(StoreModule.forRoot())
.keep(EffectsModule.forRoot())
// keeping lazy loaded module definitions
.keep(StoreFeatureModule)
.keep(EffectsFeatureModule)
);
```
7 changes: 6 additions & 1 deletion docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ module.exports = {
type: 'category',
label: 'Testing libraries',
collapsed: false,
items: ['guides/libraries/ng-select', 'guides/libraries/angular-material', 'guides/libraries/primeng'],
items: [
'guides/libraries/ng-select',
'guides/libraries/angular-material',
'guides/libraries/primeng',
'guides/libraries/ngrx',
],
},
],
};
36 changes: 34 additions & 2 deletions e2e/a11/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 34 additions & 2 deletions e2e/am/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion libs/ng-mocks/src/lib/mock-builder/promise/extract-dep.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
// Extracts dependency among flags of parameters.

const detectForwardRed = (provide: any): any => {
if (typeof provide === 'function' && provide.__forward_ref__) {
return provide();
}

return provide;
};

export default (decorators?: any[]): any => {
if (!decorators) {
return;
Expand All @@ -14,5 +23,5 @@ export default (decorators?: any[]): any => {
}
}

return provide;
return detectForwardRed(provide);
};
38 changes: 23 additions & 15 deletions libs/ng-mocks/src/lib/mock-builder/promise/init-ng-modules.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
import { mapValues } from '../../common/core.helpers';
import { flatten, mapValues } from '../../common/core.helpers';
import funcGetProvider from '../../common/func.get-provider';
import { isNgDef } from '../../common/func.is-ng-def';
import ngMocksUniverse from '../../common/ng-mocks-universe';

import initModule from './init-module';
import skipInitModule from './skip-init-module';
import { BuilderData, NgMeta } from './types';

const handleDef = ({ imports, declarations }: NgMeta, def: any, defProviders: Map<any, any>): void => {
if (isNgDef(def, 'm')) {
const extendedDef = initModule(def, defProviders);
imports.push(extendedDef);

// adding providers to touches
if (typeof extendedDef === 'object' && extendedDef.providers) {
for (const provider of flatten(extendedDef.providers)) {
ngMocksUniverse.touches.add(funcGetProvider(provider));
}
}
} else {
declarations.push(ngMocksUniverse.getBuildDeclaration(def));
}

ngMocksUniverse.touches.add(def);
};

export default ({ configDef, keepDef, mockDef, replaceDef }: BuilderData, defProviders: Map<any, any>): NgMeta => {
const { imports, declarations, providers }: NgMeta = { imports: [], declarations: [], providers: [] };
const meta: NgMeta = { imports: [], declarations: [], providers: [] };

// Adding suitable leftovers.
for (const def of [...mapValues(mockDef), ...mapValues(keepDef), ...mapValues(replaceDef)]) {
if (skipInitModule(def, configDef)) {
continue;
}

if (isNgDef(def, 'm')) {
imports.push(initModule(def, defProviders));
} else {
declarations.push(ngMocksUniverse.getBuildDeclaration(def));
}

ngMocksUniverse.touches.add(def);
handleDef(meta, def, defProviders);
}

return {
declarations,
imports,
providers,
};
return meta;
};
Loading

0 comments on commit 4a099b8

Please sign in to comment.