Skip to content

Commit

Permalink
feat: detecting global providers and mocking them
Browse files Browse the repository at this point in the history
if it has broken a test, add the broken provider via `.keep()`.
  • Loading branch information
satanTime committed Oct 29, 2020
1 parent c617e12 commit a36a9df
Show file tree
Hide file tree
Showing 20 changed files with 795 additions and 62 deletions.
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,12 @@ a type of `MockedModule<T>` and provides:
- base primitives instead of tokens with a `useValue` definition
- mocked copies of tokens with a `useValue` definition

If you get an error like: "**Component is part of the declaration of 2 modules**",
then consider usage of [`ngMocks.guts`](#ngmocks) or [`MockBuilder`](#mockbuilder).
The issue here is, that you are trying to mock a module that has a declaration which is specified in `TestBed`,
therefore, it should be handled in a better way which excludes it ([`ngMocks.guts`](#ngmocks)),
or handles internals vs externals properly ([`MockBuilder`](#mockbuilder)).

Let's imagine an Angular application where `TargetComponent` depends on a module of `DependencyModule`
and we would like to mock in a test.

Expand Down Expand Up @@ -1281,6 +1287,14 @@ The good thing here is that commonly the dependencies have been declared or impo
tested thing is. Therefore, with help of `MockBuilder` we can quite easily define a testing module,
where everything in the module will be mocked except the tested thing: `MockBuilder(TheThing, ItsModule)`.

MockBuilder tends to provide **a simple instrument to mock Angular dependencies**, does it in isolated scopes,
and has a rich toolkit that supports:

- respect of internal vs external declarations (precise exports)
- detection and creation of mocked copies for root providers
- replacement of modules and declarations in any depth
- exclusion of modules, declarations and providers in any depth

<details><summary>Click to see <strong>a code sample demonstrating ease of mocking in Angular tests</strong></summary>
<p>

Expand Down Expand Up @@ -1517,7 +1531,16 @@ beforeEach(() =>
.keep(SomeModuleComponentDirectivePipeProvider1, {
dependency: true,
})
.mock(SomeModuleComponentDirectivePipeProvider1, {
.mock(SomeModuleComponentDirectivePipe, {
dependency: true,
})
// Pass the same def as a mocked instance, if you want only to
// specify the config.
.mock(SomeProvider, SomeProvider, {
dependency: true,
})
// Or provide a mocked instance together with the config.
.mock(SomeProvider, mockedInstance, {
dependency: true,
})
.replace(SomeModuleComponentDirectivePipeProvider1, anything1, {
Expand Down
180 changes: 156 additions & 24 deletions lib/common/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@ export const extendClass = <I extends object>(base: Type<I>): Type<I> => {
(window as any).ngMocksParent = undefined;

// the next step is to respect constructor parameters as the parent class.
child.parameters = jitReflector
.parameters(parent)
.map(parameter => ngMocksUniverse.cacheMocks.get(parameter) || parameter);
child.parameters = jitReflector.parameters(parent);

return child;
};
Expand All @@ -115,15 +113,40 @@ export const isNgType = (declaration: Type<any>, type: string): boolean =>
jitReflector.annotations(declaration).some(annotation => annotation.ngMetadataName === type);

/**
* Checks whether a class was decorated by a ng type.
* m - module.
* c - component.
* d - directive.
* p - pipe.
* Checks whether a class was decorated by @NgModule.
*
* @see https://github.com/ike18t/ng-mocks#isngdef
*/
export function isNgDef(declaration: any, ngType: 'm'): declaration is Type<any>;

/**
* Checks whether a class was decorated by @Component.
*
* @see https://github.com/ike18t/ng-mocks#isngdef
*/
export function isNgDef(declaration: any, ngType: 'c'): declaration is Type<any>;

/**
* Checks whether a class was decorated by @Directive.
*
* @see https://github.com/ike18t/ng-mocks#isngdef
*/
export function isNgDef(declaration: any, ngType: 'd'): declaration is Type<any>;

/**
* Checks whether a class was decorated by @Pipe.
*
* @see https://github.com/ike18t/ng-mocks#isngdef
*/
export function isNgDef(declaration: any, ngType: 'm' | 'c' | 'd'): declaration is Type<any>;
export function isNgDef(declaration: any, ngType: 'p'): declaration is Type<PipeTransform>;

/**
* Checks whether a class was decorated by a ng type.
*
* @see https://github.com/ike18t/ng-mocks#isngdef
*/
export function isNgDef(declaration: any): declaration is Type<any>;

export function isNgDef(declaration: any, ngType?: string): declaration is Type<any> {
const isModule = (!ngType || ngType === 'm') && isNgType(declaration, 'NgModule');
const isComponent = (!ngType || ngType === 'c') && isNgType(declaration, 'Component');
Expand All @@ -133,35 +156,63 @@ export function isNgDef(declaration: any, ngType?: string): declaration is Type<
}

/**
* Checks whether a class is a mock of a class that was decorated by a ng type.
* m - module.
* c - component.
* d - directive.
* p - pipe.
* Checks whether the declaration is a mocked one and derives from the specified module.
*
* @see https://github.com/ike18t/ng-mocks#ismockedngdefof
*/
export function isMockedNgDefOf<T>(declaration: any, type: Type<T>, ngType: 'm'): declaration is Type<MockedModule<T>>;

/**
* Checks whether the declaration is a mocked one and derives from the specified component.
*
* @see https://github.com/ike18t/ng-mocks#ismockedngdefof
*/
export function isMockedNgDefOf<T>(
declaration: any,
type: Type<T>,
ngType: 'c'
): declaration is Type<MockedComponent<T>>;

/**
* Checks whether the declaration is a mocked one and derives from the specified directive.
*
* @see https://github.com/ike18t/ng-mocks#ismockedngdefof
*/
export function isMockedNgDefOf<T>(
declaration: any,
type: Type<T>,
ngType: 'd'
): declaration is Type<MockedDirective<T>>;

/**
* Checks whether the declaration is a mocked one and derives from the specified pipe.
*
* @see https://github.com/ike18t/ng-mocks#ismockedngdefof
*/
export function isMockedNgDefOf<T extends PipeTransform>(
declaration: any,
type: Type<T>,
ngType: 'p'
): declaration is Type<MockedPipe<T>>;

/**
* Checks whether the declaration is a mocked one and derives from the specified type.
*
* @see https://github.com/ike18t/ng-mocks#ismockedngdefof
*/
export function isMockedNgDefOf<T>(declaration: any, type: Type<T>): declaration is Type<T>;

export function isMockedNgDefOf<T>(declaration: any, type: Type<T>, ngType?: any): declaration is Type<T> {
return (
typeof declaration === 'function' && declaration.mockOf === type && (ngType ? isNgDef(declaration, ngType) : true)
);
}

/**
* Checks whether a variable is a real token.
*
* @see https://github.com/ike18t/ng-mocks#isnginjectiontoken
*/
export const isNgInjectionToken = (token: any): token is InjectionToken<any> =>
typeof token === 'object' && token.ngMetadataName === 'InjectionToken';

Expand All @@ -170,21 +221,44 @@ export const isNgModuleDefWithProviders = (declaration: any): declaration is NgM
declaration.ngModule !== undefined && isNgDef(declaration.ngModule, 'm');

/**
* Checks whether an object is an instance of a mocked class that was decorated by a ng type.
* m - module.
* c - component.
* d - directive.
* p - pipe.
* Checks whether the instance derives from a mocked module.
*
* @see https://github.com/ike18t/ng-mocks#ismockof
*/
export function isMockOf<T>(instance: any, declaration: Type<T>, ngType: 'm'): instance is MockedModule<T>;

/**
* Checks whether the instance derives from a mocked component.
*
* @see https://github.com/ike18t/ng-mocks#ismockof
*/
export function isMockOf<T>(instance: any, declaration: Type<T>, ngType: 'c'): instance is MockedComponent<T>;

/**
* Checks whether the instance derives from a mocked directive.
*
* @see https://github.com/ike18t/ng-mocks#ismockof
*/
export function isMockOf<T>(instance: any, declaration: Type<T>, ngType: 'd'): instance is MockedDirective<T>;

/**
* Checks whether the instance derives from a mocked pipe.
*
* @see https://github.com/ike18t/ng-mocks#ismockof
*/
export function isMockOf<T extends PipeTransform>(
instance: any,
declaration: Type<T>,
ngType: 'p'
): instance is MockedPipe<T>;

/**
* Checks whether the instance derives from a mocked type.
*
* @see https://github.com/ike18t/ng-mocks#ismockof
*/
export function isMockOf<T>(instance: any, declaration: Type<T>): instance is T;

export function isMockOf<T>(instance: any, declaration: Type<T>, ngType?: any): instance is T {
return (
typeof instance === 'object' &&
Expand All @@ -195,17 +269,40 @@ export function isMockOf<T>(instance: any, declaration: Type<T>, ngType?: any):
}

/**
* Returns a def of a mocked class based on another mock class or a source class that was decorated by a ng type.
* m - module.
* c - component.
* d - directive.
* p - pipe.
* Returns a def of a mocked module based on a mocked module or a source module.
*
* @see https://github.com/ike18t/ng-mocks#getmockedngdefof
*/
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'm'): Type<MockedModule<T>>;

/**
* Returns a def of a mocked component based on a mocked component or a source component.
*
* @see https://github.com/ike18t/ng-mocks#getmockedngdefof
*/
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'c'): Type<MockedComponent<T>>;

/**
* Returns a def of a mocked directive based on a mocked directive or a source directive.
*
* @see https://github.com/ike18t/ng-mocks#getmockedngdefof
*/
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'd'): Type<MockedDirective<T>>;

/**
* Returns a def of a mocked pipe based on a mocked pipe or a source pipe.
*
* @see https://github.com/ike18t/ng-mocks#getmockedngdefof
*/
export function getMockedNgDefOf<T>(declaration: Type<T>, type: 'p'): Type<MockedPipe<T>>;

/**
* Returns a def of a mocked class based on a mocked class or a source class decorated by a ng type.
*
* @see https://github.com/ike18t/ng-mocks#getmockedngdefof
*/
export function getMockedNgDefOf(declaration: Type<any>): Type<any>;

export function getMockedNgDefOf(declaration: any, type?: any): any {
const source = declaration.mockOf ? declaration.mockOf : declaration;
const mocks = getTestBedInjection(NG_MOCKS);
Expand Down Expand Up @@ -237,11 +334,46 @@ export function getMockedNgDefOf(declaration: any, type?: any): any {
throw new Error(`There is no mock for ${source.name}`);
}

/**
* Returns an original type.
*
* @see https://github.com/ike18t/ng-mocks#getsourceofmock
*/
export function getSourceOfMock<T>(declaration: Type<MockedModule<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://github.com/ike18t/ng-mocks#getsourceofmock
*/
export function getSourceOfMock<T>(declaration: Type<MockedComponent<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://github.com/ike18t/ng-mocks#getsourceofmock
*/
export function getSourceOfMock<T>(declaration: Type<MockedDirective<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://github.com/ike18t/ng-mocks#getsourceofmock
*/
export function getSourceOfMock<T>(declaration: Type<MockedPipe<T>>): Type<T>;

/**
* Returns an original type.
*
* @see https://github.com/ike18t/ng-mocks#getsourceofmock
*/
export function getSourceOfMock<T>(declaration: Type<T>): Type<T>;

/**
* Returns an original type.
*
* @see https://github.com/ike18t/ng-mocks#getsourceofmock
*/
export function getSourceOfMock<T>(declaration: any): Type<T> {
return typeof declaration === 'function' && declaration.mockOf ? declaration.mockOf : declaration;
}
4 changes: 2 additions & 2 deletions lib/common/ng-mocks-universe.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InjectionToken } from '@angular/core';

import { AbstractType, Type } from './lib';
import { AnyType } from './lib';

/**
* Can be changed any time.
Expand All @@ -14,5 +14,5 @@ export const ngMocksUniverse = {
config: new Map(),
flags: new Set<string>(['cacheModule', 'cacheComponent', 'cacheDirective', 'cacheProvider']),
global: new Map(),
touches: new Set<Type<any> | AbstractType<any> | InjectionToken<any>>(),
touches: new Set<AnyType<any> | InjectionToken<any>>(),
};
Loading

0 comments on commit a36a9df

Please sign in to comment.