Skip to content

Commit

Permalink
feat: global configuration for default mocks
Browse files Browse the repository at this point in the history
closes #226
  • Loading branch information
satanTime committed Dec 3, 2020
1 parent 7588499 commit 29715f8
Show file tree
Hide file tree
Showing 53 changed files with 826 additions and 102 deletions.
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,16 @@ TestBed.configureTestingModule({

Profit, now initialization of the component with the mock service does not throw the error anymore.

If we want to do it in all tests, we might use [`ngMocks.defaultMock`](#ngmocksdefaultmock).

```typescript
ngMocks.defaultMock(TodoService, () => ({
list$: () => EMPTY,
}));
```

Then, every time tests need mock `TodoService`, its `list$()` will return `EMPTY` until it has been set explicitly in `TestBed`.

Nevertheless, usually, we want not only to return a stub result as `EMPTY` observable stream,
but also to provide a fake subject, that would simulate its calls.

Expand Down Expand Up @@ -1429,7 +1439,7 @@ class AppHeaderComponent {
})
class AppModule {}

describe('MAIN', () => {
describe('main', () => {
// Usually we would have something like that.
// beforeEach(() => {
// TestBed.configureTestingModule({
Expand Down Expand Up @@ -2311,6 +2321,7 @@ describe('MockInstance', () => {
`ngMocks` provides functions to get attribute and structural directives from an element, find components and create mock objects.

- [`.guts()`](#ngmocksguts)
- [`.defaultMock()`](#ngmocksdefaultmock)
- [`.get()`](#ngmocksget)
- [`.findInstance()`](#ngmocksfindinstance)
- [`.findInstances()`](#ngmocksfindinstances)
Expand Down Expand Up @@ -2358,6 +2369,50 @@ const ngModuleMeta = ngMocks.guts(
);
```

#### ngMocks.defaultMock

Sets default mock values for the whole application.

- `ngMocks.defaultMock(MyClass, (instance, injector) => overrides)` - adds an override for a class
- `ngMocks.defaultMock(TOKEN, (value, injector) => value)` - adds an override for a token
- `ngMocks.defaultMock(MyClass)` - removes overrides
- `ngMocks.defaultMock(TOKEN)` - removes overrides

The best place to set up default mocks is `src/test.ts` for jasmine or `src/setupJest.ts` for jest.

For example if a service or a component has a property that should be `Observable`.
Then, we can configure it to be an `EMPTY` stream in the whole test suite.

```typescript
declare class MyComponent {
public url: string;
public stream$: Observable<void>;
public getStream(): Observable<void>;
}

// the returned object will be applied to the component instance.
ngMocks.defaultMock(MyComponent, () => ({
stream$: EMPTY,
getStream: () => EMPTY,
}));

// manuall override.
ngMocks.defaultMock(MyComponent, instance => {
instance.stream$ = EMPTY;
});

// overriding tokens.
ngMocks.defaultMock(MY_TOKEN, () => 'DEFAULT_VALUE');

// url will be 'DEFAULT_VALUE'.
ngMocks.defaultMock(MyComponent, (_, injector) => ({
url: injector.get(MY_TOKEN),
}));

// removing all overrdeis.
ngMocks.defaultMock(MyComponent);
```

#### ngMocks.get

Returns an attribute or structural directive which belongs to the current element.
Expand Down
2 changes: 1 addition & 1 deletion examples/MAIN/test.spec.ts → examples/main/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ class AppHeaderComponent {
})
class AppModule {}

describe('MAIN', () => {
describe('main', () => {
// Usually we would have something like that.
// beforeEach(() => {
// TestBed.configureTestingModule({
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
INJECTION_TOKEN_WE_WANT_TO_MIMIC,
} from './fixtures.tokens';

describe('NG_MOCKS:deep', () => {
describe('ng-mocks:deep', () => {
beforeEach(async () => {
const ngModule = MockBuilder(MyComponent, MyModule)
.keep(ModuleWeDontWantToMimic)
Expand Down
23 changes: 19 additions & 4 deletions lib/common/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import { EventEmitter, Injector, Optional } from '@angular/core';
import { NgControl } from '@angular/forms';

import { mapValues } from '../common/core.helpers';
import { AnyType } from '../common/core.types';
import { IMockBuilderConfig } from '../mock-builder/types';
import mockHelperStub from '../mock-helper/mock-helper.stub';
import helperMockService from '../mock-service/helper.mock-service';

import ngMocksUniverse from './ng-mocks-universe';
Expand Down Expand Up @@ -76,6 +78,22 @@ export type ngMocksMockConfig = {
viewChildRefs?: Map<string, string>;
};

const applyOverrides = (instance: any, mockOf: any, injector?: Injector): void => {
const configGlobal: Set<any> | undefined = ngMocksUniverse.getOverrides().get(mockOf);
const callbacks = configGlobal ? mapValues(configGlobal) : [];
if (ngMocksUniverse.config.get(mockOf)?.init) {
callbacks.push(ngMocksUniverse.config.get(mockOf).init);
}

for (const callback of callbacks) {
const overrides = callback(instance, injector);
if (!overrides) {
continue;
}
mockHelperStub(instance, overrides);
}
};

export class Mock {
public readonly __ngMocksConfig?: ngMocksMockConfig;
public readonly __ngMocksMock: true = true;
Expand All @@ -92,9 +110,6 @@ export class Mock {
// and faking prototype
Object.setPrototypeOf(this, mockOf.prototype);

const config = ngMocksUniverse.config.get(mockOf);
if (config && config.init && config.init) {
config.init(this, injector);
}
applyOverrides(this, mockOf, injector);
}
}
7 changes: 7 additions & 0 deletions lib/common/ng-mocks-universe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ getGlobal().ngMocksUniverse = getGlobal().ngMocksUniverse || {
cacheProviders: new Map(),
config: new Map(),
flags: new Set<string>(coreConfig.flags),
getOverrides: (): Map<any, any> => {
if (!getGlobal().ngMocksUniverse.global.has('overrides')) {
getGlobal().ngMocksUniverse.global.set('overrides', new Map());
}

return getGlobal().ngMocksUniverse.global.get('overrides');
},
global: new Map(),
isExcludedDef: (def: any): boolean =>
getGlobal().ngMocksUniverse.builtDeclarations.has(def) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BuilderData } from './types';

export default (parameters: Set<any>, mockDef: BuilderData['mockDef'], def: any): void => {
if (!skipDep(def)) {
if (mockDef.has(NG_MOCKS_ROOT_PROVIDERS) || !ngMocksUniverse.config.get('depsSkip').has(def)) {
if (mockDef.has(NG_MOCKS_ROOT_PROVIDERS) || !ngMocksUniverse.config.get('ngMocksDepsSkip').has(def)) {
parameters.add(def);
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/mock-builder/promise/add-requested-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export default (ngModule: NgMeta, { providerDef }: BuilderData): void => {
ngMocksUniverse.touches.add(provide);

if (provide !== provider && (provider as any).deps) {
extractDependency((provider as any).deps, ngMocksUniverse.config.get('deps'));
extractDependency((provider as any).deps, ngMocksUniverse.config.get('ngMocksDeps'));
}
}
};
4 changes: 2 additions & 2 deletions lib/mock-builder/promise/get-root-provider-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export default (mockDef: BuilderData['mockDef']): Set<any> => {
continue;
}
checkRootProviderDependency(provide, touched, bucket);
if (mockDef.has(NG_MOCKS_ROOT_PROVIDERS) || !ngMocksUniverse.config.get('depsSkip').has(def)) {
if (mockDef.has(NG_MOCKS_ROOT_PROVIDERS) || !ngMocksUniverse.config.get('ngMocksDepsSkip').has(def)) {
parameters.add(provide);
} else {
ngMocksUniverse.config.get('depsSkip').add(provide);
ngMocksUniverse.config.get('ngMocksDepsSkip').add(provide);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions lib/mock-builder/promise/get-root-providers-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export default (): {
} => {
// We need buckets here to process first all depsSkip, then deps and only after that all other defs.
const buckets: any[] = [
mapValues(ngMocksUniverse.config.get('depsSkip')),
mapValues(ngMocksUniverse.config.get('deps')),
mapValues(ngMocksUniverse.config.get('ngMocksDepsSkip')),
mapValues(ngMocksUniverse.config.get('ngMocksDeps')),
mapValues(ngMocksUniverse.touches),
];

Expand Down
10 changes: 6 additions & 4 deletions lib/mock-builder/promise/handle-root-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { mapValues } from '../../common/core.helpers';
import { NG_MOCKS_ROOT_PROVIDERS } from '../../common/core.tokens';
import { isNgInjectionToken } from '../../common/func.is-ng-injection-token';
import ngMocksUniverse from '../../common/ng-mocks-universe';
import helperMockService from '../../mock-service/helper.mock-service';
import helperResolveProvider from '../../mock-service/helper.resolve-provider';
import helperUseFactory from '../../mock-service/helper.use-factory';

import getRootProviderParameters from './get-root-provider-parameters';
import { BuilderData, NgMeta } from './types';
Expand All @@ -14,12 +15,13 @@ export default (ngModule: NgMeta, { keepDef, mockDef }: BuilderData): void => {
if (parameters.size) {
const parametersMap = new Map();
for (const parameter of mapValues(parameters)) {
const mock = helperMockService.resolveProvider(parameter, parametersMap);
const mock = helperResolveProvider(parameter, parametersMap);
if (mock) {
ngModule.providers.push(mock);
} else if (isNgInjectionToken(parameter)) {
const multi = ngMocksUniverse.config.has('multi') && ngMocksUniverse.config.get('multi').has(parameter);
ngModule.providers.push(helperMockService.useFactory(parameter, () => (multi ? [] : undefined)));
const multi =
ngMocksUniverse.config.has('ngMocksMulti') && ngMocksUniverse.config.get('ngMocksMulti').has(parameter);
ngModule.providers.push(helperUseFactory(parameter, () => (multi ? [] : undefined)));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion lib/mock-builder/promise/init-exclude-def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export default (excludeDef: Set<any>): void => {
for (const def of [...mapValues(excludeDef)]) {
ngMocksUniverse.builtDeclarations.set(def, null);
ngMocksUniverse.builtProviders.set(def, null);
ngMocksUniverse.config.get('resolution').set(def, 'exclude');
ngMocksUniverse.config.get('ngMocksDepsResolution').set(def, 'exclude');
}
};
2 changes: 1 addition & 1 deletion lib/mock-builder/promise/init-keep-def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export default (keepDef: Set<any>): void => {
for (const def of mapValues(keepDef)) {
ngMocksUniverse.builtDeclarations.set(def, def);
ngMocksUniverse.builtProviders.set(def, def);
ngMocksUniverse.config.get('resolution').set(def, 'keep');
ngMocksUniverse.config.get('ngMocksDepsResolution').set(def, 'keep');
}
};
2 changes: 1 addition & 1 deletion lib/mock-builder/promise/init-mock-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import tryMockProvider from './try-mock-provider';

export default (mockDef: Set<any>, defValue: Map<any, any>): void => {
for (const def of mapValues(mockDef)) {
ngMocksUniverse.config.get('resolution').set(def, 'mock');
ngMocksUniverse.config.get('ngMocksDepsResolution').set(def, 'mock');
tryMockDeclaration(def, defValue);
tryMockProvider(def, defValue);

Expand Down
2 changes: 1 addition & 1 deletion lib/mock-builder/promise/init-replace-def.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import ngMocksUniverse from '../../common/ng-mocks-universe';
export default (replaceDef: Set<any>, defValue: Map<any, any>): void => {
for (const def of mapValues(replaceDef)) {
ngMocksUniverse.builtDeclarations.set(def, defValue.get(def));
ngMocksUniverse.config.get('resolution').set(def, 'replace');
ngMocksUniverse.config.get('ngMocksDepsResolution').set(def, 'replace');
}
};
12 changes: 8 additions & 4 deletions lib/mock-builder/promise/init-universe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ export default ({
}: BuilderData): Map<any, any> => {
ngMocksUniverse.flags.add('cachePipe');

ngMocksUniverse.config.set('multi', new Set()); // collecting multi flags of providers.
ngMocksUniverse.config.set('deps', new Set()); // collecting all deps of providers.
ngMocksUniverse.config.set('depsSkip', new Set()); // collecting all declarations of kept modules.
ngMocksUniverse.config.set('resolution', new Map()); // flags to understand how to mock nested declarations.
// collecting multi flags of providers.
ngMocksUniverse.config.set('ngMocksMulti', new Set());
// collecting all deps of providers.
ngMocksUniverse.config.set('ngMocksDeps', new Set());
// collecting all declarations of kept modules.
ngMocksUniverse.config.set('ngMocksDepsSkip', new Set());
// flags to understand how to mock nested declarations.
ngMocksUniverse.config.set('ngMocksDepsResolution', new Map());
for (const [k, v] of mapEntries(configDef)) {
ngMocksUniverse.config.set(k, v);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/mock-builder/promise/skip-root-provider-dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export default (provide: any): boolean => {
return true;
}

return ngMocksUniverse.config.get('depsSkip').has(provide);
return ngMocksUniverse.config.get('ngMocksDepsSkip').has(provide);
};
15 changes: 9 additions & 6 deletions lib/mock-builder/promise/try-mock-provider.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { isNgDef } from '../../common/func.is-ng-def';
import ngMocksUniverse from '../../common/ng-mocks-universe';
import helperMockService from '../../mock-service/helper.mock-service';
import mockHelperStub from '../../mock-helper/mock-helper.stub';
import helperUseFactory from '../../mock-service/helper.use-factory';
import mockProvider from '../../mock-service/mock-provider';
import { MockService } from '../../mock-service/mock-service';
import { IMockBuilderConfigMock } from '../types';

const createInstance = (def: any, instance: any, config: IMockBuilderConfigMock, isFunc: boolean): any => {
const createInstance = (existing: any, instance: any, config: IMockBuilderConfigMock, isFunc: boolean): any => {
const params = isFunc ? { transform: instance } : instance;
if (config.precise) {
return params;
}

return config.precise ? instance : MockService(def, params);
return mockHelperStub(existing, params);
};

export default (def: any, defValue: Map<any, any>): void => {
Expand All @@ -18,7 +21,7 @@ export default (def: any, defValue: Map<any, any>): void => {
const isFunc = isNgDef(def, 'p') && typeof instance === 'function';
ngMocksUniverse.builtProviders.set(
def,
helperMockService.useFactory(def, () => createInstance(def, instance, config, isFunc)),
helperUseFactory(def, undefined, existing => createInstance(existing, instance, config, isFunc)),
);
} else if (isNgDef(def, 'i')) {
ngMocksUniverse.builtProviders.set(def, mockProvider(def));
Expand All @@ -28,7 +31,7 @@ export default (def: any, defValue: Map<any, any>): void => {
const instance = defValue.get(def);
ngMocksUniverse.builtProviders.set(
def,
helperMockService.useFactory(def, () => instance),
helperUseFactory(def, undefined, () => instance),
);
} else if (!isNgDef(def)) {
ngMocksUniverse.builtProviders.set(def, mockProvider(def));
Expand Down
File renamed without changes.
18 changes: 18 additions & 0 deletions lib/mock-helper/mock-helper.default-mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { InjectionToken, Injector } from '@angular/core';

import { AnyType } from '../common/core.types';
import ngMocksUniverse from '../common/ng-mocks-universe';

export default <T>(
def: AnyType<T> | InjectionToken<T> | string,
callback?: (instance: undefined | T, injector: Injector) => void | Partial<T>,
): void => {
const map = ngMocksUniverse.getOverrides();
if (callback) {
const set: Set<any> = map.has(def) ? map.get(def) : new Set();
set.add(callback);
map.set(def, set);
} else {
map.delete(def);
}
};
2 changes: 1 addition & 1 deletion lib/mock-helper/mock-helper.faster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { getTestBed, TestBed } from '@angular/core/testing';

import ngMocksUniverse from '../common/ng-mocks-universe';

import mockHelperFlushTestBed from './mock-helper.flushTestBed';
import mockHelperFlushTestBed from './mock-helper.flush-test-bed';

export default () => {
beforeAll(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { By } from '@angular/platform-browser';
import { getSourceOfMock } from '../common/func.get-source-of-mock';

import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parseFindArgs';
import funcParseFindArgs from './func.parse-find-args';

export default (...args: any[]) => {
const { el, sel } = funcParseFindArgs(args);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Type } from '../common/core.types';
import { getSourceOfMock } from '../common/func.get-source-of-mock';

import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parseFindArgs';
import mockHelperFindInstances from './mock-helper.findInstances';
import funcParseFindArgs from './func.parse-find-args';
import mockHelperFindInstances from './mock-helper.find-instances';

const defaultNotFoundValue = {}; // simulating Symbol

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getSourceOfMock } from '../common/func.get-source-of-mock';
import { MockedDebugNode } from '../mock-render/types';

import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parseFindArgs';
import funcParseFindArgs from './func.parse-find-args';

function nestedCheck<T>(
result: T[],
Expand Down
2 changes: 1 addition & 1 deletion lib/mock-helper/mock-helper.find.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { By } from '@angular/platform-browser';
import { getSourceOfMock } from '../common/func.get-source-of-mock';

import funcGetLastFixture from './func.get-last-fixture';
import funcParseFindArgs from './func.parseFindArgs';
import funcParseFindArgs from './func.parse-find-args';

const defaultNotFoundValue = {}; // simulating Symbol

Expand Down
File renamed without changes.

0 comments on commit 29715f8

Please sign in to comment.