Skip to content

Commit

Permalink
feat: support to inject a library-related service mocker
Browse files Browse the repository at this point in the history
Closes #87
  • Loading branch information
satanTime committed Mar 29, 2020
1 parent 28b452b commit cc07240
Show file tree
Hide file tree
Showing 13 changed files with 171 additions and 14 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,20 @@ returns first found attribute or structural directive which belongs to current e
`MockHelper.findDirectives(fixture.debugElement, Directive)`
returns all found attribute or structural directives which belong to current element and all its child.

## Auto Spy

Add the next code to `src/test.ts` if you want all mocked methods and functions to be a jasmine spy.

```typescript
import 'ng-mocks/dist/jasmine';
```

In case of jest.

```typescript
import 'ng-mocks/dist/jest';
```

## Other examples of tests

More detailed examples can be found in
Expand Down
19 changes: 19 additions & 0 deletions e2e/spies/fixtures.components.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { TargetService } from './fixtures.providers';

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

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

public echo(): string {
return this.service.echo('TargetComponent');
}
}
7 changes: 7 additions & 0 deletions e2e/spies/fixtures.modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { TargetService } from './fixtures.providers';

@NgModule({
providers: [TargetService],
})
export class TargetModule {}
10 changes: 10 additions & 0 deletions e2e/spies/fixtures.providers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Injectable } from '@angular/core';

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

public echo(value?: string): string {
return value ? value : this.value;
}
}
73 changes: 73 additions & 0 deletions e2e/spies/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { inject, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { MockModule, MockRender } from 'ng-mocks';

import { TargetComponent } from './fixtures.components';
import { TargetModule } from './fixtures.modules';
import { TargetService } from './fixtures.providers';
import createSpyObj = jasmine.createSpyObj;
import Spy = jasmine.Spy;

describe('spies:real', () => {
beforeEach(() =>
TestBed.configureTestingModule({
declarations: [TargetComponent],
imports: [TargetModule],
}).compileComponents()
);

it('should render', () => {
const fixture = MockRender(TargetComponent);
const component = fixture.debugElement.query(By.directive(TargetComponent)).componentInstance as TargetComponent;
expect(component).toBeDefined();
expect(component.echo()).toEqual('TargetComponent');
});
});

describe('spies:manual-mock', () => {
beforeEach(() => {
const spy = createSpyObj<TargetService>('TargetService', ['echo']);
spy.echo.and.returnValue('fake');

return TestBed.configureTestingModule({
declarations: [TargetComponent],
imports: [MockModule(TargetModule)],
providers: [
{
provide: TargetService,
useValue: spy,
},
],
}).compileComponents();
});

it('should get manually mocked service', inject([TargetService], (targetService: TargetService) => {
const fixture = MockRender(TargetComponent);
const component = fixture.debugElement.query(By.directive(TargetComponent)).componentInstance as TargetComponent;
expect(component).toBeDefined();
expect(targetService.echo).toHaveBeenCalledTimes(1);
expect(targetService.echo).toHaveBeenCalledWith('constructor');
expect(component.echo()).toEqual('fake');
expect(targetService.echo).toHaveBeenCalledTimes(2); // tslint:disable-line:no-magic-numbers
}));
});

describe('spies:auto-mock', () => {
beforeEach(() =>
TestBed.configureTestingModule({
declarations: [TargetComponent],
imports: [MockModule(TargetModule)],
}).compileComponents()
);

it('should get already mocked service', inject([TargetService], (targetService: TargetService) => {
const fixture = MockRender(TargetComponent);
const component = fixture.debugElement.query(By.directive(TargetComponent)).componentInstance as TargetComponent;
expect(component).toBeDefined();
expect(targetService.echo).toHaveBeenCalledTimes(1);
expect(targetService.echo).toHaveBeenCalledWith('constructor');
(targetService.echo as Spy).and.returnValue('faked');
expect(component.echo()).toEqual('faked');
expect(targetService.echo).toHaveBeenCalledTimes(2); // tslint:disable-line:no-magic-numbers
}));
});
5 changes: 5 additions & 0 deletions jasmine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { mockServiceHelper } from './lib/mock-service';

declare const jasmine: any;

mockServiceHelper.registerMockFunction(jasmine.createSpy);
5 changes: 5 additions & 0 deletions jest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { mockServiceHelper } from './lib/mock-service';

declare const jest: any;

mockServiceHelper.registerMockFunction(jest.fn);
1 change: 1 addition & 0 deletions karma.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ module.exports = (config: any) => {
'node_modules/zone.js/dist/fake-async-test.js',
'karma-test-shim.ts',
'index.ts',
'jasmine.ts',
{ pattern: 'lib/**/*.ts' },
{ pattern: 'e2e/**/*.ts' },
{ pattern: 'examples/**/*.ts' },
Expand Down
2 changes: 1 addition & 1 deletion lib/common/Mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class Mock {
if ((this as any)[method]) {
continue;
}
(this as any)[method] = mockServiceHelper.mockFunction(this, method);
(this as any)[method] = mockServiceHelper.mockFunction();
}
for (const output of (this as any).__mockedOutputs) {
if ((this as any)[output]) {
Expand Down
2 changes: 1 addition & 1 deletion lib/mock-component/mock-component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe('MockComponent', () => {
});

it('should allow spying of viewchild component methods', () => {
const spy = spyOn(component.childComponent, 'performAction');
const spy = component.childComponent.performAction;
component.performActionOnChild('test');
expect(spy).toHaveBeenCalledWith('test');
});
Expand Down
2 changes: 1 addition & 1 deletion lib/mock-directive/mock-directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe('MockDirective', () => {
});

it('should allow spying of viewchild directive methods', () => {
const spy = spyOn(component.childDirective, 'performAction');
const spy = component.childDirective.performAction;
component.performActionOnChild('test');
expect(spy).toHaveBeenCalledWith('test');
});
Expand Down
43 changes: 33 additions & 10 deletions lib/mock-service/mock-service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
export type MockedFunction = () => undefined;

/**
* @internal
*/
export const mockServiceHelper = {
mockFunction: (object?: {}, method?: string): MockedFunction => () => undefined,
let customMockFunction: (() => MockedFunction) | undefined;

const mockServiceHelperPrototype = {
mockFunction: (): MockedFunction => {
if (customMockFunction) {
return customMockFunction();
}
return () => undefined;
},

registerMockFunction: (mockFunction: typeof customMockFunction) => {
customMockFunction = mockFunction;
},

createMockFromPrototype: (service: any): { [key: string]: MockedFunction } => {
const methods = mockServiceHelper.extractMethodsFromPrototype(service);
const methods = mockServiceHelperPrototype.extractMethodsFromPrototype(service);
const value: { [key: string]: MockedFunction } = {};
for (const method of methods) {
value[method] = mockServiceHelper.mockFunction(value, method);
value[method] = mockServiceHelperPrototype.mockFunction();
}
return value;
},
Expand All @@ -36,21 +44,36 @@ export const mockServiceHelper = {
},
};

// We need a single pointer to the object among all environments.
((window as any) || (global as any)).ngMocksMockServiceHelper =
((window as any) || (global as any)).ngMocksMockServiceHelper || mockServiceHelperPrototype;

const localHelper: typeof mockServiceHelperPrototype = ((window as any) || (global as any)).ngMocksMockServiceHelper;

/**
* @internal
*/
export const mockServiceHelper: {
extractMethodsFromPrototype(service: any): string[];
mockFunction(): MockedFunction;
registerMockFunction(mockFunction: (() => MockedFunction) | undefined): void;
} = ((window as any) || (global as any)).ngMocksMockServiceHelper;

export function MockService(service?: boolean | number | string | null): undefined;
export function MockService<T extends {}>(service: T): any;
export function MockService(service: any): any {
// mocking all methods / properties of a class / object.
let value: any;
if (typeof service === 'function' && service.prototype) {
value = mockServiceHelper.createMockFromPrototype(service.prototype);
value = localHelper.createMockFromPrototype(service.prototype);
} else if (typeof service === 'function') {
value = mockServiceHelper.mockFunction();
value = localHelper.mockFunction();
} else if (Array.isArray(service)) {
value = [];
} else if (typeof service === 'object' && service !== null && service.ngMetadataName !== 'InjectionToken') {
value =
typeof service.constructor === 'function' && service.constructor.prototype
? mockServiceHelper.createMockFromPrototype(service.constructor.prototype)
? localHelper.createMockFromPrototype(service.constructor.prototype)
: {};
for (const property of Object.keys(service)) {
const mock = MockService(service[property]);
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
},
"skipLibCheck": true
},
"include": ["index.ts"]
"include": ["index.ts", "jasmine.ts", "jest.ts"]
}

0 comments on commit cc07240

Please sign in to comment.