Skip to content

Commit

Permalink
Add MockPropertyPolicy
Browse files Browse the repository at this point in the history
  • Loading branch information
johanblumenberg committed May 22, 2018
1 parent a520724 commit b6f4c70
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 13 deletions.
24 changes: 20 additions & 4 deletions src/Mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import {MockableFunctionsFinder} from "./utils/MockableFunctionsFinder";
import {ObjectInspector} from "./utils/ObjectInspector";
import {ObjectPropertyCodeRetriever} from "./utils/ObjectPropertyCodeRetriever";

export enum MockPropertyPolicy {
StubAsProperty,
StubAsMethod,
Throw
}

export class Mocker {
protected objectInspector = new ObjectInspector();
private methodStubCollections: any = {};
Expand All @@ -18,8 +24,8 @@ export class Mocker {
private mockableFunctionsFinder = new MockableFunctionsFinder();
private objectPropertyCodeRetriever = new ObjectPropertyCodeRetriever();

constructor(private clazz: any, intf: boolean, protected instance: any = {}) {
this.mock.__tsmockitoInterface = intf;
constructor(private clazz: any, policy: MockPropertyPolicy, protected instance: any = {}) {
this.mock.__policy = policy;

this.mock.__tsmockitoInstance = this.instance;
this.mock.__tsmockitoMocker = this;
Expand All @@ -45,15 +51,25 @@ export class Mocker {
get: (target: any, name: PropertyKey) => {
const hasMethodStub = name in target;
if (!hasMethodStub) {
if (this.mock.__tsmockitoInterface) {
if (this.mock.__policy === MockPropertyPolicy.StubAsMethod) {
if (origin !== "instance" || name !== "then") {
// Don't make this mock object instance look like a Promise instance by mistake, if someone is checking
this.createMethodStub(name.toString());
this.createInstanceActionListener(name.toString(), {});
}
} else {
} else if (this.mock.__policy === MockPropertyPolicy.StubAsProperty) {
this.createPropertyStub(name.toString());
this.createInstancePropertyDescriptorListener(name.toString(), {}, this.clazz.prototype);
} else if (this.mock.__policy === MockPropertyPolicy.Throw) {
if (origin === "instance") {
throw new Error("Trying to read property " + name.toString() + " from a mock object, which was not expected.");
} else {
// TODO: Assuming it is a property, not a function. Fix this...
this.createPropertyStub(name.toString());
this.createInstancePropertyDescriptorListener(name.toString(), {}, this.clazz.prototype);
}
} else {
throw new Error("Invalid MockPolicy value");
}
}
return target[name];
Expand Down
4 changes: 2 additions & 2 deletions src/Spy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as _ from "lodash";
import {Mocker} from "./Mock";
import {Mocker,MockPropertyPolicy} from "./Mock";
import {RealMethod} from "./spy/RealMethod";
import {CallThroughMethodStub} from "./stub/CallThroughMethodStub";
import {MethodStub} from "./stub/MethodStub";
Expand All @@ -8,7 +8,7 @@ export class Spy extends Mocker {
private realMethods: { [key: string]: RealMethod };

constructor(instance: any) {
super(instance.constructor, false, instance);
super(instance.constructor, MockPropertyPolicy.StubAsProperty, instance);

if (_.isObject(instance)) {
this.processProperties(instance);
Expand Down
13 changes: 8 additions & 5 deletions src/ts-mockito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@ import {StrictEqualMatcher} from "./matcher/type/StrictEqualMatcher";
import {MethodStubSetter} from "./MethodStubSetter";
import {MethodStubVerificator} from "./MethodStubVerificator";
import {MethodToStub} from "./MethodToStub";
import {Mocker} from "./Mock";
import {Mocker,MockPropertyPolicy} from "./Mock";
import {Spy} from "./Spy";

export {MockPropertyPolicy} from "./Mock";

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 {
return new Mocker(clazz, false).getMock();
export function mock<T>(clazz: { new(...args: any[]): T; } | (Function & { prototype: T }), policy: MockPropertyPolicy = MockPropertyPolicy.StubAsProperty ): T {
return new Mocker(clazz, policy).getMock();
}

export function imock<T>(): T {
export function imock<T>(policy: MockPropertyPolicy = MockPropertyPolicy.StubAsMethod): T {
class Empty {}
const mockedValue = new Mocker(Empty, true).getMock();
const mockedValue = new Mocker(Empty, policy).getMock();

if (typeof Proxy === "undefined") {
throw new Error("Mocking of interfaces requires support for Proxy objects");
Expand Down Expand Up @@ -157,4 +159,5 @@ export default {
strictEqual,
match,
objectContaining,
MockPropertyPolicy,
};
88 changes: 86 additions & 2 deletions test/mocking.types.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {MethodToStub} from "../src/MethodToStub";
import {imock, instance, mock, verify, when} from "../src/ts-mockito";
import {imock, instance, mock, verify, when, MockPropertyPolicy} from "../src/ts-mockito";
import {Bar} from "./utils/Bar";

describe("mocking", () => {
Expand Down Expand Up @@ -153,7 +153,7 @@ describe("mocking", () => {
});
});

describe("mocking an interface", () => {
describe("mocking an interface with methods", () => {
let mockedFoo: SampleInterface;
let foo: SampleInterface;

Expand Down Expand Up @@ -206,6 +206,85 @@ describe("mocking", () => {
verify(mockedFoo.sampleMethod()).called();
expect(result).toBe(5);
});

it("can return default value from actions with no setup", () => {
// given
mockedFoo = imock();
foo = instance(mockedFoo);

// when
const result = foo.sampleMethod();

// then
verify(mockedFoo.sampleMethod()).called();
expect(result).toBe(null);
});
}
});

describe("mock an interface with properties", () => {
let mockedFoo: SamplePropertyInterface;
let foo: SamplePropertyInterface;

if (typeof Proxy !== "undefined") {
it("can setup call actions", () => {
// given
mockedFoo = imock(MockPropertyPolicy.StubAsProperty);
foo = instance(mockedFoo);
when(mockedFoo.foo).thenReturn("value");

// when
const result = foo.foo;

// then
verify(mockedFoo.foo).called();
expect(result).toBe("value");
});

it("can return default value from actions with no setup", () => {
// given
mockedFoo = imock(MockPropertyPolicy.StubAsProperty);
foo = instance(mockedFoo);

// when
const result = foo.foo;

// then
verify(mockedFoo.foo).called();
expect(result).toBe(null);
});
}
});

describe("mock an interface with default policy to throw", () => {
let mockedFoo: SamplePropertyInterface;
let foo: SamplePropertyInterface;

if (typeof Proxy !== "undefined") {
it("can setup call actions", () => {
// given
mockedFoo = imock(MockPropertyPolicy.Throw);
foo = instance(mockedFoo);
when(mockedFoo.foo).thenReturn("value");

// when
const result = foo.foo;

// then
verify(mockedFoo.foo).called();
expect(result).toBe("value");
});

it("can throw from actions with no setup", () => {
// given
mockedFoo = imock(MockPropertyPolicy.Throw);
foo = instance(mockedFoo);

// when
expect(() => foo.foo).toThrow();

// then
});
}
});
});
Expand Down Expand Up @@ -236,6 +315,11 @@ interface SampleInterface {
sampleMethod(): number;
}

interface SamplePropertyInterface {
foo: string;
bar: number;
}

class SampleInterfaceImplementation implements SampleInterface {
public dependency: Bar;

Expand Down

0 comments on commit b6f4c70

Please sign in to comment.