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
closes #103
  • Loading branch information
satanTime committed Apr 17, 2020
1 parent 28b452b commit 85adc0b
Show file tree
Hide file tree
Showing 15 changed files with 286 additions and 24 deletions.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -439,9 +439,56 @@ returns attribute or structural directive which belongs to current element.
`MockHelper.findDirective(fixture.debugElement, Directive)` -
returns first found attribute or structural directive which belongs to current element or any child.

`MockHelper.findDirectives(fixture.debugElement, Directive)`
`MockHelper.findDirectives(fixture.debugElement, Directive)` -
returns all found attribute or structural directives which belong to current element and all its child.

`MockHelper.mockService(instance, methodName)` -
returns a mocked function / spy of the method. If the method hasn't been mocked yet - mocks it.

`MockHelper.mockService(instance, propertyName, 'get' | 'set')` -
returns a mocked function / spy of the property. If the property hasn't been mocked yet - mocks it.

```typescript
// The example below uses auto spy.
it('mocks getters, setters and methods in a way that jasmine can mock them w/o an issue', () => {
const mock: GetterSetterMethodHuetod = MockService(GetterSetterMethodHuetod);
expect(mock).toBeDefined();

// Creating a mock on the getter.
MockHelper.mockService<Spy>(mock, 'name', 'get').and.returnValue('mock');
expect(mock.name).toEqual('mock');

// Creating a mock on the setter.
MockHelper.mockService(mock, 'name', 'set');
mock.name = 'mock';
expect(MockHelper.mockService(mock, 'name', 'set')).toHaveBeenCalledWith('mock');

// Creating a mock on the method.
MockHelper.mockService<Spy>(mock, 'nameMethod').and.returnValue('mock');
expect(mock.nameMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'nameMethod')).toHaveBeenCalledWith('mock');

// Creating a mock on the method that doesn't exist.
MockHelper.mockService<Spy>(mock, 'fakeMethod').and.returnValue('mock');
expect((mock as any).fakeMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'fakeMethod')).toHaveBeenCalledWith('mock');
});
```

## 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
5 changes: 5 additions & 0 deletions lib/mock-helper/mock-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import { DebugNode, Type } from '@angular/core';

import { MockedFunction, mockServiceHelper } from '../mock-service';

interface INestedNodes extends DebugNode {
childNodes?: INestedNodes[];
}
Expand Down Expand Up @@ -70,4 +72,7 @@ export const MockHelper = {
});
return result;
},

mockService: <T = MockedFunction>(instance: any, name: string, style?: 'get' | 'set'): T =>
mockServiceHelper.mock(instance, name, style),
};
46 changes: 45 additions & 1 deletion lib/mock-service/mock-service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MockService } from 'ng-mocks';
import { MockHelper, MockService } from 'ng-mocks';
import Spy = jasmine.Spy;

// tslint:disable:max-classes-per-file
class DeepParentClass {
Expand Down Expand Up @@ -35,6 +36,25 @@ class ChildClass extends ParentClass {
}
}

class GetterSetterMethodHuetod {
public nameValue = 'nameValue';

get name(): string {
return `${this.nameValue}${this.nameValue}`;
}

set name(value: string) {
this.nameValue = value;
}

public nameMethod(value?: string): string {
if (value) {
this.name = value;
}
return this.name;
}
}

// tslint:enable:max-classes-per-file

describe('MockService', () => {
Expand Down Expand Up @@ -147,4 +167,28 @@ describe('MockService', () => {
expect(mockedService.func2()).toBeUndefined('func2()');
expect(mockedService.func3()).toBeUndefined('func3()');
});

it('mocks getters, setters and methods in a way that jasmine can mock them w/o an issue', () => {
const mock: GetterSetterMethodHuetod = MockService(GetterSetterMethodHuetod);
expect(mock).toBeDefined();

// Creating a mock on the getter.
MockHelper.mockService<Spy>(mock, 'name', 'get').and.returnValue('mock');
expect(mock.name).toEqual('mock');

// Creating a mock on the setter.
MockHelper.mockService(mock, 'name', 'set');
mock.name = 'mock';
expect(MockHelper.mockService(mock, 'name', 'set')).toHaveBeenCalledWith('mock');

// Creating a mock on the method.
MockHelper.mockService<Spy>(mock, 'nameMethod').and.returnValue('mock');
expect(mock.nameMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'nameMethod')).toHaveBeenCalledWith('mock');

// Creating a mock on the method that doesn't exist.
MockHelper.mockService<Spy>(mock, 'fakeMethod').and.returnValue('mock');
expect((mock as any).fakeMethod('mock')).toEqual('mock');
expect(MockHelper.mockService(mock, 'fakeMethod')).toHaveBeenCalledWith('mock');
});
});
Loading

0 comments on commit 85adc0b

Please sign in to comment.