Skip to content

Commit

Permalink
fix: respecting initialization of providers between tests
Browse files Browse the repository at this point in the history
closes #186
  • Loading branch information
satanTime committed Sep 13, 2020
1 parent 37c74e7 commit 2c7b47d
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 56 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ Our tests:
* [MockDeclaration](#mockdeclarations)
* [MockModule](#mockmodule)

* [Reactive Forms Components](#mocked-reactive-forms-components)
* [Structural Components](#usage-example-of-structural-directives)
* [Auto Spy](#auto-spy)
- [Reactive Forms Components](#mocked-reactive-forms-components)
- [Structural Components](#usage-example-of-structural-directives)
- [Auto Spy](#auto-spy)

---

Expand Down Expand Up @@ -949,7 +949,8 @@ ngMocks provides functions to get attribute and structural directives from an el
* ngMocks.stub(service, methods)
* ngMocks.stub(service, property, 'get' | 'set')

- ngMocks.flushTestBed()
- ngMocks.flushTestBed() - flushes initialization of TestBed
- ngMocks.reset() - resets caches of ngMocks

<details><summary>Click to see <strong>a usage example</strong></summary>
<p>
Expand Down
6 changes: 5 additions & 1 deletion lib/mock-builder/mock-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,11 @@ export class MockBuilderPromise implements PromiseLike<IMockBuilderResult> {
// mocking requested things.
for (const def of mapValues(this.mockDef.provider)) {
if (this.mockDef.providerMock.has(def)) {
ngMocksUniverse.builder.set(def, mockServiceHelper.useFactory(def, this.mockDef.providerMock.get(def)));
const instance = this.mockDef.providerMock.get(def);
ngMocksUniverse.builder.set(
def,
mockServiceHelper.useFactory(def, () => instance)
);
} else {
ngMocksUniverse.builder.set(def, MockProvider(def));
}
Expand Down
12 changes: 12 additions & 0 deletions lib/mock-helper/mock-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getTestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';

import { AbstractType, getSourceOfMock, isNgInjectionToken, Type } from '../common';
import { ngMocksUniverse } from '../common/ng-mocks-universe';
import { directiveResolver } from '../common/reflect';
import { MockedDebugElement, MockedDebugNode } from '../mock-render';
import { MockedFunction, mockServiceHelper } from '../mock-service';
Expand Down Expand Up @@ -119,6 +120,8 @@ export const ngMocks: {
output<T = any>(debugNode: MockedDebugNode, output: string): EventEmitter<T>;
output<T = any, D = undefined>(debugNode: MockedDebugNode, output: string, notFoundValue: D): D | EventEmitter<T>;

reset(): void;

stub<T = MockedFunction>(instance: any, name: string, style?: 'get' | 'set'): T;
stub<I extends object, O extends object>(instance: I, overrides: O): I & O;
} = {
Expand Down Expand Up @@ -320,4 +323,13 @@ export const ngMocks: {
testBed._moduleFactory = undefined;
testBed._testModuleRef = null;
},

reset(): void {
ngMocksUniverse.builder = new Map();
ngMocksUniverse.cacheMocks = new Map();
ngMocksUniverse.cacheProviders = new Map();
ngMocksUniverse.config = new Map();
ngMocksUniverse.flags = new Set(['cacheModule', 'cacheComponent', 'cacheDirective', 'cacheProvider']);
ngMocksUniverse.touches = new Set();
},
};
3 changes: 1 addition & 2 deletions lib/mock-module/mock-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ export function MockProvider(provider: any): Provider | undefined {
return provider;
}

const mockedProvider = mockServiceHelper.useFactory(
ngMocksUniverse.cacheMocks.get(provide) || provide,
const mockedProvider = mockServiceHelper.useFactory(ngMocksUniverse.cacheMocks.get(provide) || provide, () =>
MockService(provide)
);
if (ngMocksUniverse.flags.has('cacheProvider')) {
Expand Down
11 changes: 6 additions & 5 deletions lib/mock-service/mock-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,17 @@ const mockServiceHelperPrototype = {
return multi && typeof mockedDef === 'object' ? { ...mockedDef, multi } : mockedDef;
},

useFactory<D, I>(def: D, mock: I): Provider {
useFactory<D, I>(def: D, mock: () => I): Provider {
return {
deps: [Injector],
provide: def,
useFactory: (injector?: Injector) => {
const instance = mock();
const config = ngMocksUniverse.config.get(def);
if (injector && mock && config && config.init) {
config.init(mock, injector);
if (injector && instance && config && config.init) {
config.init(instance, injector);
}
return mock;
return instance;
},
};
},
Expand Down Expand Up @@ -336,7 +337,7 @@ export const mockServiceHelper: {
registerMockFunction(mockFunction: (mockName: string) => MockedFunction | undefined): void;
replaceWithMocks(value: any): any;
resolveProvider(def: Provider, resolutions: Map<any, any>, changed?: (flag: boolean) => void): Provider;
useFactory<D, I>(def: D, instance: I): Provider;
useFactory<D, I>(def: D, instance: () => I): Provider;
} = ((window as any) || (global as any)).ngMocksMockServiceHelper;

export function MockService(service?: boolean | number | string | null, mockNamePrefix?: string): undefined;
Expand Down
60 changes: 60 additions & 0 deletions tests/issue-186/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Injectable, NgModule } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { MockModule } from 'ng-mocks';

@Injectable()
class ExampleProvider {
a: number;
}
@NgModule({
providers: [ExampleProvider],
})
class ExampleModule {}

describe('issue-186:real', () => {
let exampleProvider: ExampleProvider;
let exampleProviderFromSetupPhase: ExampleProvider;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [ExampleModule],
});

exampleProvider = TestBed.get(ExampleProvider);
});

it('should not be able to pass state between tests (setup phase)', () => {
exampleProviderFromSetupPhase = exampleProvider;
expect(exampleProvider.a).toBeUndefined();
exampleProvider.a = 11;
});

it('should not be able to pass state between tests (validation phase)', () => {
expect(exampleProvider).not.toBe(exampleProviderFromSetupPhase);
expect(exampleProvider.a).toBeUndefined();
});
});

describe('issue-186:mock', () => {
let exampleProvider: ExampleProvider;
let exampleProviderFromSetupPhase: ExampleProvider;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [MockModule(ExampleModule)],
});

exampleProvider = TestBed.get(ExampleProvider);
});

it('should not be able to pass state between tests (setup phase)', () => {
exampleProviderFromSetupPhase = exampleProvider;
expect(exampleProvider.a).toBeUndefined();
exampleProvider.a = 11;
});

it('should not be able to pass state between tests (validation phase)', () => {
expect(exampleProvider).not.toBe(exampleProviderFromSetupPhase);
expect(exampleProvider.a).toBeUndefined();
});
});
20 changes: 0 additions & 20 deletions tests/spies/fixtures.components.ts

This file was deleted.

8 changes: 0 additions & 8 deletions tests/spies/fixtures.modules.ts

This file was deleted.

10 changes: 0 additions & 10 deletions tests/spies/fixtures.providers.ts

This file was deleted.

44 changes: 38 additions & 6 deletions tests/spies/test.spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
import { Component, Injectable, NgModule } from '@angular/core';
import { inject } from '@angular/core/testing';
import { MockBuilder, MockRender, MockService, ngMocks } from 'ng-mocks';

import { TargetComponent } from './fixtures.components';
import { TargetModule } from './fixtures.modules';
import { TargetService } from './fixtures.providers';

// fix to support both jasmine and jest in the test
declare const jest: any;
declare const jasmine: any;

@Injectable()
class TargetService {
protected value = 'TargetService';

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

@Component({
selector: 'target',
template: '<ng-content></ng-content>',
})
class TargetComponent {
protected service: TargetService;

constructor(service: TargetService) {
this.service = service;
this.service.echo('constructor');
}

public echo(): string {
return this.service.echo('TargetComponent');
}
}

@NgModule({
declarations: [TargetComponent],
providers: [TargetService],
})
class TargetModule {}

describe('spies:real', () => {
beforeEach(() => MockBuilder(TargetComponent).keep(TargetModule));

Expand All @@ -28,11 +57,13 @@ describe('spies:manual-mock', () => {
} else if (typeof jasmine !== 'undefined') {
ngMocks.stub<any>(spy, 'echo').and.returnValue('fake');
}
(spy as any).manual = true;

return MockBuilder(TargetComponent, TargetModule).mock(TargetService, spy);
});

it('should get manually mocked service', inject([TargetService], (targetService: TargetService) => {
expect((targetService as any).manual).toBe(true);
const fixture = MockRender(TargetComponent);
const component = ngMocks.find(fixture.debugElement, TargetComponent).componentInstance;
expect(component).toBeDefined();
Expand All @@ -46,8 +77,9 @@ describe('spies:manual-mock', () => {
describe('spies:auto-mock', () => {
beforeEach(() => MockBuilder(TargetComponent, TargetModule));

it('should get already mocked service', inject([TargetService], (targetService: TargetService) => {
it('should get already mocked service', () => {
const fixture = MockRender(TargetComponent);
const targetService = fixture.point.injector.get(TargetService);
const component = ngMocks.find(fixture.debugElement, TargetComponent).componentInstance;
expect(component).toBeDefined();
expect(targetService.echo).toHaveBeenCalledTimes(1);
Expand All @@ -59,5 +91,5 @@ describe('spies:auto-mock', () => {
}
expect(component.echo()).toEqual('faked');
expect(targetService.echo).toHaveBeenCalledTimes(2);
}));
});
});

0 comments on commit 2c7b47d

Please sign in to comment.