Skip to content

Commit

Permalink
fix(mock-intance): graceful reset after specs
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Dec 25, 2020
1 parent 92abb82 commit 2e395df
Show file tree
Hide file tree
Showing 8 changed files with 347 additions and 125 deletions.
242 changes: 122 additions & 120 deletions README.md

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions lib/common/ng-mocks-universe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface NgMocksUniverse {
cacheProviders: Map<any, any>;
config: Map<any, any>;
flags: Set<string>;
getLocalMocks: () => Array<[any, any]>;
getOverrides: () => Map<any, any>;
global: Map<any, any>;
isExcludedDef: (def: any) => boolean;
Expand All @@ -21,7 +22,7 @@ interface NgMocksUniverse {
}

getGlobal().ngMocksUniverse = getGlobal().ngMocksUniverse || {};
const ngMocksUniverse = getGlobal().ngMocksUniverse;
const ngMocksUniverse: NgMocksUniverse = getGlobal().ngMocksUniverse;

ngMocksUniverse.builtDeclarations = new Map();
ngMocksUniverse.builtProviders = new Map();
Expand All @@ -32,7 +33,15 @@ ngMocksUniverse.flags = new Set(coreConfig.flags);
ngMocksUniverse.global = new Map();
ngMocksUniverse.touches = new Set();

ngMocksUniverse.getOverrides = (): Map<any, any> => {
ngMocksUniverse.getLocalMocks = () => {
if (!ngMocksUniverse.global.has('local-mocks')) {
ngMocksUniverse.global.set('local-mocks', []);
}

return ngMocksUniverse.global.get('local-mocks');
};

ngMocksUniverse.getOverrides = () => {
if (!ngMocksUniverse.global.has('overrides')) {
ngMocksUniverse.global.set('overrides', new Map());
}
Expand Down
32 changes: 32 additions & 0 deletions lib/mock-instance/mock-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@ import { InjectionToken, Injector } from '@angular/core';
import { AbstractType, Type } from '../common/core.types';
import ngMocksUniverse from '../common/ng-mocks-universe';

let installReporter = true;
const restore = (declaration: any, config: any): void => {
if (installReporter) {
jasmine.getEnv().addReporter(reporter);
installReporter = false;
}

ngMocksUniverse.getLocalMocks().push([declaration, config]);
};

const reporter: jasmine.CustomReporter = {
specDone: () => {
const set = ngMocksUniverse.getLocalMocks();
while (set.length) {
const [declaration, config] = set.pop() || /* istanbul ignore next */ [];
const universeConfig = ngMocksUniverse.config.has(declaration) ? ngMocksUniverse.config.get(declaration) : {};
ngMocksUniverse.config.set(declaration, {
...universeConfig,
...config,
});
}
},
specStarted: () => {
// On start we have to flush any caches,
// they are not from this spec.
const set = ngMocksUniverse.getLocalMocks();
set.splice(0, set.length);
},
};

/**
* @see https://github.com/ike18t/ng-mocks#mockinstance
*/
Expand Down Expand Up @@ -42,6 +72,8 @@ export function MockInstance<T>(
export function MockInstance<T>(declaration: Type<T> | AbstractType<T> | InjectionToken<T>, data?: any) {
const config = typeof data === 'function' ? { init: data } : data;
const universeConfig = ngMocksUniverse.config.has(declaration) ? ngMocksUniverse.config.get(declaration) : {};
restore(declaration, universeConfig);

if (config) {
ngMocksUniverse.config.set(declaration, {
...universeConfig,
Expand Down
49 changes: 49 additions & 0 deletions tests-failures/mock-instance-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { MockInstance } from 'ng-mocks';

class TargetService {
protected name = 'target';

public echo1(): string {
return this.name;
}

public echo2(): string {
return this.name;
}
}

// Accepts valid type.
MockInstance(TargetService, () => ({
echo1: () => 'mock',
}));
// Accepts valid type.
MockInstance(TargetService, {
init: () => ({
echo2: () => 'mock',
}),
});
// Accepts internal change.
MockInstance(TargetService, instance => {
instance.echo1 = () => 'mock';
});
// Accepts internal change.
MockInstance(TargetService, {
init: instance => {
instance.echo1 = () => 'mock';
},
});
// Accepts reset.
MockInstance(TargetService);

// @ts-expect-error: does not support wrong types.
MockInstance(TargetService, () => true);
// @ts-expect-error: does not support wrong types.
MockInstance(TargetService, () => ({
prop: 123,
}));
MockInstance(TargetService, {
// @ts-expect-error: does not support wrong types.
init: () => ({
prop: 123,
}),
});
30 changes: 30 additions & 0 deletions tests-failures/mock-instance-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { InjectionToken } from '@angular/core';
import { MockInstance } from 'ng-mocks';

const TOKEN = new InjectionToken<{ prop: boolean }>('TOKEN');

// Accepts valid type.
MockInstance(TOKEN, () => ({
prop: true,
}));
// Accepts valid type.
MockInstance(TOKEN, {
init: () => ({
prop: false,
}),
});
// Accepts reset.
MockInstance(TOKEN);

// @ts-expect-error: does not support wrong types.
MockInstance(TOKEN, () => true);
// @ts-expect-error: does not support wrong types.
MockInstance(TOKEN, () => ({
prop: 123,
}));
// @ts-expect-error: does not support wrong types.
MockInstance(TOKEN, {
init: () => ({
prop: 123,
}),
});
6 changes: 3 additions & 3 deletions tests/interceptor-kept-mocked/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ describe('interceptor-kept-mock', () => {

beforeAll(() => {
MockInstance(Target2Interceptor, {
init: instance =>
(instance.intercept = (request, next) =>
next.handle(request)),
init: instance => {
instance.intercept = (request, next) => next.handle(request);
},
});
});

Expand Down
57 changes: 57 additions & 0 deletions tests/mock-instance-in-it/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Injectable } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import {
MockBuilder,
MockInstance,
MockReset,
ngMocks,
} from 'ng-mocks';

@Injectable()
class TargetService {
protected name = 'target';

public echo(): string {
return this.name;
}
}

describe('mock-instance-in-it', () => {
beforeAll(() =>
MockInstance(TargetService, () => ({
echo: () => 'beforeAll',
})),
);

afterAll(() => {
// need to call it remove cached TargetService.
ngMocks.flushTestBed();

const actual = TestBed.get(TargetService).echo();
expect(actual).toEqual('beforeAll');

MockReset();
});

beforeEach(() => MockBuilder().mock(TargetService));

beforeEach(() =>
MockInstance(TargetService, () => ({
echo: () => 'beforeEach',
})),
);

it('overrides in the 1st it', () => {
MockInstance(TargetService, () => ({
echo: () => 'it',
}));

const actual = TestBed.get(TargetService).echo();
expect(actual).toEqual('it');
});

it('uses beforeEach in the 2nd it', () => {
const actual = TestBed.get(TargetService).echo();
expect(actual).toEqual('beforeEach');
});
});
43 changes: 43 additions & 0 deletions tests/mock-instance-token/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
Inject,
Injectable,
InjectionToken,
NgModule,
} from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { MockBuilder, MockInstance } from 'ng-mocks';

const TOKEN = new InjectionToken<string>('TOKEN');

@Injectable()
class TargetService {
public constructor(
@Inject(TOKEN) protected readonly name: string,
) {}

public echo(): string {
return this.name;
}
}

@NgModule({
providers: [
TargetService,
{
provide: TOKEN,
useValue: 'target',
},
],
})
class TargetModule {}

describe('mock-instance-token', () => {
beforeEach(() => MockBuilder(TargetService, TargetModule));

it('provides tokens', () => {
MockInstance(TOKEN, () => 'mock');

const actual = TestBed.get(TargetService).echo();
expect(actual).toEqual('mock');
});
});

0 comments on commit 2e395df

Please sign in to comment.