Skip to content

Commit

Permalink
Support for interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
NagRock committed Jul 20, 2019
1 parent 29e35d3 commit e1155e2
Show file tree
Hide file tree
Showing 9 changed files with 690 additions and 613 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ts-mockito",
"version": "2.3.2",
"version": "2.4.0",
"description": "Mocking library for TypeScript",
"main": "lib/ts-mockito.js",
"typings": "lib/ts-mockito",
Expand Down
23 changes: 23 additions & 0 deletions src/MethodStubSetter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,35 @@ export class MethodStubSetter<T, ResolveType = void, RejectType = Error> {
this.groupIndex = ++MethodStubSetter.globalGroupIndex;
}

public thenDoNothing(...rest: T[]): this {
this.convertToPropertyIfIsNotAFunction();
return this;
}

public thenReturn(...rest: T[]): this {
this.convertToPropertyIfIsNotAFunction();
rest.forEach(value => {
this.methodToStub.methodStubCollection.add(new ReturnValueMethodStub(this.groupIndex, this.methodToStub.matchers, value));
});
return this;
}

public thenThrow(...rest: Error[]): this {
this.convertToPropertyIfIsNotAFunction();
rest.forEach(error => {
this.methodToStub.methodStubCollection.add(new ThrowErrorMethodStub(this.groupIndex, this.methodToStub.matchers, error));
});
return this;
}

public thenCall(func: (...args: any[]) => any): this {
this.convertToPropertyIfIsNotAFunction();
this.methodToStub.methodStubCollection.add(new CallFunctionMethodStub(this.groupIndex, this.methodToStub.matchers, func));
return this;
}

public thenResolve(...rest: ResolveType[]): this {
this.convertToPropertyIfIsNotAFunction();
// Resolves undefined if no resolve values are given.
if (rest.length === 0) {
rest.push(undefined);
Expand All @@ -44,6 +53,7 @@ export class MethodStubSetter<T, ResolveType = void, RejectType = Error> {
}

public thenReject(...rest: Error[]): this {
this.convertToPropertyIfIsNotAFunction();
// Resolves undefined if no resolve values are given.
if (rest.length === 0) {
rest.push(new Error(`mocked '${this.methodToStub.name}' rejected`));
Expand All @@ -53,4 +63,17 @@ export class MethodStubSetter<T, ResolveType = void, RejectType = Error> {
});
return this;
}

private convertToPropertyIfIsNotAFunction(): void {
if (!this.methodToStub.methodStubCollection) {
const info = (this.methodToStub as any)("__tsMockitoGetInfo");
delete info.mocker.mock[info.key];
delete info.mocker.instance[info.key];

info.mocker.createPropertyStub(info.key);
info.mocker.createInstancePropertyDescriptorListener(info.key, {}, undefined);
info.mocker.createInstanceActionListener(info.key, undefined);
this.methodToStub = info.mocker.mock[info.key];
}
}
}
47 changes: 43 additions & 4 deletions src/Mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,32 +11,65 @@ import {ObjectInspector} from "./utils/ObjectInspector";
import {ObjectPropertyCodeRetriever} from "./utils/ObjectPropertyCodeRetriever";

export class Mocker {
public mock: any = {};
protected objectInspector = new ObjectInspector();
private methodStubCollections: any = {};
private methodActions: MethodAction[] = [];
private mock: any = {};
private mockableFunctionsFinder = new MockableFunctionsFinder();
private objectPropertyCodeRetriever = new ObjectPropertyCodeRetriever();
private excludedPropertyNames: string[] = ["hasOwnProperty"];

constructor(private clazz: any, protected instance: any = {}) {
constructor(private clazz: any, public instance: any = {}) {
this.mock.__tsmockitoInstance = this.instance;
this.mock.__tsmockitoMocker = this;
if (_.isObject(this.clazz) && _.isObject(this.instance)) {
this.processProperties((this.clazz as any).prototype);
this.processClassCode(this.clazz);
this.processFunctionsCode((this.clazz as any).prototype);
}
if (typeof Proxy !== "undefined") {
if (typeof Proxy !== "undefined" && this.clazz) {
this.mock.__tsmockitoInstance = new Proxy(this.instance, this.createCatchAllHandlerForRemainingPropertiesWithoutGetters());
} else if (typeof Proxy !== "undefined" && !this.clazz) {
this.instance = new Proxy(this.instance, {
get: (target: any, name: PropertyKey) => {
if (this.excludedPropertyNames.indexOf(name.toString()) >= 0) {
return target[name];
}

const hasMethodStub = name in target;

if (!hasMethodStub) {
return this.createActionListener(name.toString());
}
return target[name];
},
});
this.mock.__tsmockitoInstance = this.instance;
}
}

public getMock(): any {
if (typeof Proxy === "undefined") {
return this.mock;
}
return new Proxy(this.mock, this.createCatchAllHandlerForRemainingPropertiesWithoutGetters());
if (typeof Proxy !== "undefined" && this.clazz) {
return new Proxy(this.mock, this.createCatchAllHandlerForRemainingPropertiesWithoutGetters());
}
return new Proxy(this.mock, {
get: (target: any, name: PropertyKey) => {
const hasProp = name in target;
if (hasProp) {
return target[name];
}

const hasMethodStub = name in target;
if (!hasMethodStub) {
this.createMethodStub(name.toString());
this.createInstanceActionListener(name.toString(), {});
}
return this.mock[name.toString()];
},
});
}

public createCatchAllHandlerForRemainingPropertiesWithoutGetters(): any {
Expand Down Expand Up @@ -176,6 +209,12 @@ export class Mocker {

private createMethodToStub(key: string): () => any {
return (...args) => {
if (args.length === 1 && args[0] === "__tsMockitoGetInfo") {
return {
key,
mocker: this,
};
}
if (!this.methodStubCollections[key]) {
this.methodStubCollections[key] = new MethodStubCollection();
}
Expand Down
3 changes: 2 additions & 1 deletion src/ts-mockito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export function spy<T>(instanceToSpy: T): T {
return new Spy(instanceToSpy).getMock();
}

export function mock<T>(clazz: (new(...args: any[]) => T) | (Function & { prototype: T }) ): T {
export function mock<T>(clazz?: any): T;
export function mock<T>(clazz?: (new(...args: any[]) => T) | (Function & { prototype: T }) ): T {
return new Mocker(clazz).getMock();
}

Expand Down
26 changes: 26 additions & 0 deletions test/interface.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { instance, mock, when } from "../src/ts-mockito";
import { FooInterface } from "./utils/FooInterface";

describe("Interface", () => {
let mockedFoo: FooInterface;
let foo: FooInterface;

if (typeof Proxy === "undefined") {
pending("Testing browser doesn't support Proxy.");
}

beforeEach(() => {
mockedFoo = mock<FooInterface>();
foo = instance(mockedFoo);
});

it("should mock interface function", () => {
// Rest cases for interfaces are tested in verification.spec.ts

// when
when(mockedFoo.sumByInterface(2, 3)).thenReturn(1000);

// then
expect(foo.sumByInterface(2, 3)).toEqual(1000);
});
});
22 changes: 0 additions & 22 deletions test/mocking.getter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {MethodToStub} from "../src/MethodToStub";
import {instance, mock, when} from "../src/ts-mockito";
import {Bar} from "./utils/Bar";

Expand All @@ -18,16 +17,6 @@ describe("mocking", () => {

});

it("does create own property descriptors on mock", () => {
// given

// when
mockedFoo = mock(FooWithGetterAndSetter);

// then
expect((mockedFoo.twoPlusTwo as any) instanceof MethodToStub).toBe(true);
});

it("does create own property descriptors on instance", () => {
// given
mockedFoo = mock(FooWithGetterAndSetter);
Expand All @@ -40,17 +29,6 @@ describe("mocking", () => {
expect(foo.twoPlusTwo).toBe(42);
});

it("does create inherited property descriptors on mock", () => {
// given
mockedFoo = mock(FooWithGetterAndSetter);
foo = instance(mockedFoo);

// when

// then
expect((mockedFoo.sampleString as any) instanceof MethodToStub).toBe(true);
});

it("does create inherited property descriptors on instance", () => {
// given
mockedFoo = mock(FooWithGetterAndSetter);
Expand Down
1 change: 1 addition & 0 deletions test/mocking.properties.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ describe("mocking", () => {
// given
mockedFoo = mock(FooWithProperties);
foo = instance(mockedFoo);
when(mockedFoo.sampleNumber).thenDoNothing();

// when
const value = foo.sampleNumber;
Expand Down
4 changes: 4 additions & 0 deletions test/utils/FooInterface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface FooInterface {
sumByInterface(a: number, b: number): number;
sampleFunctionWithoutArgs(): number;
}
Loading

0 comments on commit e1155e2

Please sign in to comment.