Skip to content

Commit

Permalink
feat: MockService
Browse files Browse the repository at this point in the history
  • Loading branch information
satanTime committed Mar 14, 2020
1 parent daaa204 commit 62a87ea
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 37 deletions.
40 changes: 14 additions & 26 deletions docker/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
#!/bin/bash
echo "${0}:start"
set -e

child_pid=0
parent_pid=$$

trap catch_exits TERM KILL INT

catch_exits() {
echo "${0}:stop '${child_pid}'"
kill ${child_pid} &
wait
echo "${0}:exit"
exit 1
fork() {
echo ${@} | xargs -t -I % sh -c "%"
}

set -e
command=${@}

if [[ ! "${ENTRYPOINT_SKIP}" ]]; then
for file in `ls -v /docker-entrypoint.d/*.sh`
do
echo "${0}:entry '${file}'"
sh -c "${file}"
if [[ "${?}" != "0" ]]; then
exit 1;
fi
done

fork() {
echo "${0}:command '${command}'"
sh -c "${command}"
}
do
fork ${file} &
child_pid=$!
wait ${child_pid}
if [[ "${?}" != "0" ]]; then
exit 1;
fi
done
fi

fork &
fork ${@} &
child_pid=$!
echo "${0}:child '${child_pid}'"
wait ${child_pid}
3 changes: 2 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './lib/mock-component';
export * from './lib/mock-declaration';
export * from './lib/mock-directive';
export * from './lib/mock-helper';
export * from './lib/mock-render';
export * from './lib/mock-module';
export * from './lib/mock-pipe';
export * from './lib/mock-render';
export * from './lib/mock-service';
26 changes: 21 additions & 5 deletions lib/mock-module/mock-module.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ import { Component } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BrowserModule, By } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MockComponent, MockModule, MockRender } from 'ng-mocks';

import { MockComponent } from '../mock-component';

import { MockModule } from './mock-module';
import {
AppRoutingModule,
AppRoutingModule, CustomWithServiceComponent,
ExampleComponent,
ExampleConsumerComponent,
LogicNestedModule,
LogicRootModule,
ModuleWithProvidersModule,
ParentModule,
SameImports1Module,
SameImports2Module
SameImports2Module, WithServiceModule,
} from './test-fixtures';

@Component({
Expand Down Expand Up @@ -201,6 +199,24 @@ describe('Usage of cached nested module', () => {

});

describe('WithServiceModule', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
CustomWithServiceComponent,
],
imports: [
MockModule(WithServiceModule),
],
});
}));

it('should not throw an error of service method', () => {
const fixture = MockRender('<custom-service></custom-service>');
expect(fixture).toBeDefined();
});
});

// TODO> Doesn't work because ParentModule doesn't export anything.
// TODO> Basically it's feature of ng-mocks to export declarations of mocked modules.
// describe('RealModule', () => {
Expand Down
9 changes: 4 additions & 5 deletions lib/mock-module/mock-module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule, Provider, Type } from '@angular/core';
import { MockDeclaration, MockOf, MockService } from 'ng-mocks';

import { MockOf } from '../common';
import { jitReflector, ngModuleResolver } from '../common/reflect';
import { MockDeclaration } from '../mock-declaration';

const cache = new Map<Type<NgModule>, Type<NgModule>>();

Expand Down Expand Up @@ -44,20 +43,20 @@ const mockProvider = (provider: any): Provider | undefined => {
typeof provide === 'object' && provide.ngMetadataName === 'InjectionToken'
&& neverMockProvidedToken.includes(provide.toString())
) {
return undefined;
return provider;
}

if (
typeof provide === 'function'
&& neverMockProvidedFunction.includes(provide.name)
) {
return undefined;
return provider;
}

return {
multi,
provide,
useValue: {},
useValue: MockService(provide),
};
};

Expand Down
25 changes: 25 additions & 0 deletions lib/mock-module/test-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,29 @@ class ModuleProvider {
})
export class ModuleWithProvidersModule {}

// Checking services
@Injectable()
export class CustomService {
protected readonly value = 'dummy';

public getSomething(): string {
return this.value;
}
}
@Component({
selector: 'custom-service',
template: `same imports`
})
export class CustomWithServiceComponent {
public name: string;

constructor(service: CustomService) {
this.name = service.getSomething();
}
}
@NgModule({
providers: [CustomService],
})
export class WithServiceModule {}

/* Assets for ModuleWithProviders END */
1 change: 1 addition & 0 deletions lib/mock-service/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './mock-service';
150 changes: 150 additions & 0 deletions lib/mock-service/mock-service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { MockService } from 'ng-mocks';

// tslint:disable:max-classes-per-file
class DeepParentClass {
public deepParentMethodName = 'deepParentMethod';

public deepParentMethod() {
return this.deepParentMethodName;
}
}

class ParentClass extends DeepParentClass {
public overrideMeName = 'overrideMe';
public parentMethodName = 'parentMethod';

public overrideMe() {
return this.overrideMeName;
}

public parentMethod() {
return this.parentMethodName;
}
}

class ChildClass extends ParentClass {
public childMethodName = 'childMethod';
public overrideMeName = 'childOverrideMe';

public childMethod() {
return this.childMethodName;
}

public overrideMe() {
return this.overrideMeName;
}
}

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

describe('MockService', () => {
it('should convert boolean, number, string, null and undefined to undefined', () => {
expect(MockService(true)).toBeUndefined();
expect(MockService(false)).toBeUndefined();
expect(MockService(0)).toBeUndefined();
expect(MockService(1)).toBeUndefined();
expect(MockService(-1)).toBeUndefined();
expect(MockService(NaN)).toBeUndefined();
expect(MockService('')).toBeUndefined();
expect(MockService(null)).toBeUndefined(); // tslint:disable-line:no-null-keyword
expect(MockService(undefined)).toBeUndefined();
});

it('should convert an array of anything to an empty array', () => {
expect(MockService([1, 0, 1])).toEqual([]);
expect(MockService([new DeepParentClass()])).toEqual([]);
});

it('should convert functions to () => undefined', () => {
const mockedService = MockService(() => 0);
expect(mockedService).toEqual(jasmine.any(Function), 'mockedService');
expect(mockedService()).toBeUndefined();
});

it('should mock own methods of a class without a parent', () => {
const mockedService = MockService(DeepParentClass);

// all properties should be undefined, maybe defined as getters and setters.
expect(mockedService.deepParentMethodName).toBeUndefined('deepParentMethodName');

// all methods should be defined as functions which return undefined.
expect(mockedService.deepParentMethod).toEqual(jasmine.any(Function), 'deepParentMethod');
expect(mockedService.deepParentMethod()).toBeUndefined('deepParentMethod()');
});

it('should mock own and parent methods of a class', () => {
const mockedService = MockService(ChildClass);

// all properties should be undefined, maybe defined as getters and setters.
expect(mockedService.deepParentMethodName).toBeUndefined('deepParentMethodName');
expect(mockedService.parentMethodName).toBeUndefined('parentMethodName');
expect(mockedService.overrideMeName).toBeUndefined('overrideMeName');
expect(mockedService.childMethodName).toBeUndefined('childMethodName');

// all methods should be defined as functions which return undefined.
expect(mockedService.deepParentMethod).toEqual(jasmine.any(Function), 'deepParentMethod');
expect(mockedService.deepParentMethod()).toBeUndefined('deepParentMethod()');
expect(mockedService.parentMethod).toEqual(jasmine.any(Function), 'parentMethod');
expect(mockedService.parentMethod()).toBeUndefined('parentMethod()');
expect(mockedService.overrideMe).toEqual(jasmine.any(Function), 'overrideMe');
expect(mockedService.overrideMe()).toBeUndefined('overrideMe()');
expect(mockedService.childMethod).toEqual(jasmine.any(Function), 'childMethod');
expect(mockedService.childMethod()).toBeUndefined('childMethod()');
});

it('should mock an instance of a class as an object', () => {
const mockedService = MockService(new ChildClass());

// all properties should be undefined, maybe defined as getters and setters.
expect(mockedService.deepParentMethodName).toBeUndefined('deepParentMethodName');
expect(mockedService.parentMethodName).toBeUndefined('parentMethodName');
expect(mockedService.overrideMeName).toBeUndefined('overrideMeName');
expect(mockedService.childMethodName).toBeUndefined('childMethodName');

// all methods should be defined as functions which return undefined.
expect(mockedService.deepParentMethod).toEqual(jasmine.any(Function), 'deepParentMethod');
expect(mockedService.deepParentMethod()).toBeUndefined('deepParentMethod()');
expect(mockedService.parentMethod).toEqual(jasmine.any(Function), 'parentMethod');
expect(mockedService.parentMethod()).toBeUndefined('parentMethod()');
expect(mockedService.overrideMe).toEqual(jasmine.any(Function), 'overrideMe');
expect(mockedService.overrideMe()).toBeUndefined('overrideMe()');
expect(mockedService.childMethod).toEqual(jasmine.any(Function), 'childMethod');
expect(mockedService.childMethod()).toBeUndefined('childMethod()');
});

it('should mock own and nested properties of an object', () => {
const mockedService = MockService({
booleanFalse: false,
booleanTrue: true,
child1: {
child11: {
func1: () => 0,
nullValue: null, // tslint:disable-line:no-null-keyword
undefinedValue: undefined,
},
number0: 0,
number1: 1,
},
child2: {
stringEmpty: '',
},
func2: () => 1,
func3: () => false,
});

expect(mockedService).toEqual({
child1: {
child11: {
func1: jasmine.any(Function),
},
},
child2: {},
func2: jasmine.any(Function),
func3: jasmine.any(Function),
});

expect(mockedService.child1.child11.func1()).toBeUndefined('func1()');
expect(mockedService.func2()).toBeUndefined('func2()');
expect(mockedService.func3()).toBeUndefined('func3()');
});
});
45 changes: 45 additions & 0 deletions lib/mock-service/mock-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
function MockClass(service: any): any {
const value: any = {};
let prototype = service;
while (Object.getPrototypeOf(prototype) !== null) {
for (const method of Object.getOwnPropertyNames(prototype)) {
if (method === 'constructor') {
continue;
}

const descriptor = Object.getOwnPropertyDescriptor(prototype, method);
const isGetterSetter = descriptor && (descriptor.get || descriptor.set);
if (!isGetterSetter && !value[method]) {
value[method] = () => undefined;
}
}
prototype = Object.getPrototypeOf(prototype);
}
return value;
}

export function MockService(service: boolean | number | string | null | undefined): 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 = MockClass(service.prototype);
} else if (typeof service === 'function') {
value = () => undefined;
} else if (Array.isArray(service)) {
value = [];
} else if (typeof service === 'object' && service !== null && service.ngMetadataName !== 'InjectionToken') {
value = typeof service.constructor === 'function' && service.constructor.prototype
? MockClass(service.constructor.prototype)
: {};
for (const property of Object.keys(service)) {
const mock = MockService(service[property]);
if (mock !== undefined) {
value[property] = mock;
}
}
}

return value;
}

0 comments on commit 62a87ea

Please sign in to comment.