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 28, 2020
1 parent 28b452b commit 52945fd
Show file tree
Hide file tree
Showing 13 changed files with 161 additions and 10 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,17 @@ 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/mockery/jasmine';
```
In case of jest.
```typescript
import 'ng-mocks/dist/mockery/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');
}
}
10 changes: 10 additions & 0 deletions e2e/spies/fixtures.modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
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;
}
}
69 changes: 69 additions & 0 deletions e2e/spies/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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
}));
});
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',
'mockery/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
30 changes: 24 additions & 6 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 value: { [key: string]: MockedFunction } = {};
for (const method of methods) {
value[method] = mockServiceHelper.mockFunction(value, method);
value[method] = mockServiceHelper.mockFunction();
}
return value;
},
Expand All @@ -36,6 +44,16 @@ 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;

/**
* @internal
*/
export const mockServiceHelper: typeof mockServiceHelperPrototype =
(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 {
Expand Down
5 changes: 5 additions & 0 deletions mockery/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 mockery/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);
5 changes: 4 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@
},
"skipLibCheck": true
},
"include": ["index.ts"]
"include": ["index.ts",
"mockery/jasmine.ts",
"mockery/jest.ts"
]
}

0 comments on commit 52945fd

Please sign in to comment.