@fluffy-spoon/substitute
Advanced tools
Comparing version 1.0.52 to 1.0.53
@@ -6,3 +6,3 @@ export declare class Example { | ||
v: string; | ||
foo(): void; | ||
foo(): string; | ||
} |
@@ -28,3 +28,3 @@ "use strict"; | ||
Example.prototype.foo = function () { | ||
console.log('stuff'); | ||
return 'stuff'; | ||
}; | ||
@@ -34,10 +34,52 @@ return Example; | ||
exports.Example = Example; | ||
var instance; | ||
var substitute; | ||
ava_1.default.beforeEach(function () { | ||
instance = new Example(); | ||
substitute = Index_1.Substitute.for(); | ||
}); | ||
ava_1.default('are arguments equal', function (t) { | ||
t.true(Utilities_1.areArgumentsEqual(Index_1.Arg.any(), 'hi')); | ||
t.true(Utilities_1.areArgumentsEqual(Index_1.Arg.any('array'), ['foo', 'bar'])); | ||
t.false(Utilities_1.areArgumentsEqual(['foo', 'bar'], ['foo', 'bar'])); | ||
t.true(Utilities_1.areArgumentsEqual(Index_1.Arg.any('array'), ['foo', 'bar'])); | ||
t.false(Utilities_1.areArgumentsEqual(Index_1.Arg.any('array'), 1337)); | ||
}); | ||
ava_1.default('class method returns with placeholder args', function (t) { | ||
substitute.c(Index_1.Arg.any(), "there").returns("blah", "haha"); | ||
t.deepEqual(substitute.c("hi", "there"), 'blah'); | ||
t.deepEqual(substitute.c("hi", "the1re"), void 0); | ||
t.deepEqual(substitute.c("his", "there"), 'haha'); | ||
t.deepEqual(substitute.c("his", "there"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
}); | ||
ava_1.default('partial mocks using function mimicks with specific args', function (t) { | ||
substitute.c('a', 'b').mimicks(instance.c); | ||
t.deepEqual(substitute.c('c', 'b'), void 0); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
ava_1.default('class method returns with specific args', function (t) { | ||
substitute.c("hi", "there").returns("blah", "haha"); | ||
t.deepEqual(substitute.c("hi", "there"), 'blah'); | ||
t.deepEqual(substitute.c("hi", "the1re"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), 'haha'); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
}); | ||
ava_1.default('class string field get received', function (t) { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(function () { return substitute.received(3).a; }); | ||
t.notThrows(function () { return substitute.received().a; }); | ||
t.notThrows(function () { return substitute.received(4).a; }); | ||
}); | ||
ava_1.default('partial mocks using function mimicks with all args', function (t) { | ||
substitute.c(Index_1.Arg.all()).mimicks(instance.c); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
ava_1.default('partial mocks using property instance mimicks', function (t) { | ||
substitute.d.mimicks(function () { return instance.d; }); | ||
t.deepEqual(substitute.d, 1337); | ||
}); | ||
ava_1.default('class void returns', function (t) { | ||
@@ -71,19 +113,2 @@ substitute.foo().returns(void 0, null); | ||
}); | ||
ava_1.default('class string field get received', function (t) { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(function () { return substitute.received(3).a; }); | ||
t.notThrows(function () { return substitute.received().a; }); | ||
t.notThrows(function () { return substitute.received(4).a; }); | ||
}); | ||
ava_1.default('class method returns', function (t) { | ||
substitute.c("hi", "there").returns("blah", "haha"); | ||
t.deepEqual(substitute.c("hi", "there"), 'blah'); | ||
t.deepEqual(substitute.c("hi", "the1re"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), 'haha'); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
}); | ||
ava_1.default('class method received', function (t) { | ||
@@ -90,0 +115,0 @@ void substitute.c("hi", "there"); |
@@ -0,2 +1,14 @@ | ||
export declare class Argument<T> { | ||
private description; | ||
private matchingFunction; | ||
constructor(description: string, matchingFunction: (arg: T) => boolean); | ||
matches(arg: T): boolean; | ||
toString(): string; | ||
inspect(): string; | ||
} | ||
export declare class AllArguments extends Argument<any> { | ||
constructor(); | ||
} | ||
export declare class Arg { | ||
static all(): AllArguments; | ||
static any(): any; | ||
@@ -12,9 +24,1 @@ static any<T extends 'string'>(type: T): Argument<string> & string; | ||
} | ||
export declare class Argument<T> { | ||
private description; | ||
private matchingFunction; | ||
constructor(description: string, matchingFunction: (arg: T) => boolean); | ||
matches(arg: T): boolean; | ||
toString(): string; | ||
inspect(): string; | ||
} |
"use strict"; | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
} | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var Argument = /** @class */ (function () { | ||
function Argument(description, matchingFunction) { | ||
this.description = description; | ||
this.matchingFunction = matchingFunction; | ||
} | ||
Argument.prototype.matches = function (arg) { | ||
return this.matchingFunction(arg); | ||
}; | ||
Argument.prototype.toString = function () { | ||
return this.description; | ||
}; | ||
Argument.prototype.inspect = function () { | ||
return this.description; | ||
}; | ||
return Argument; | ||
}()); | ||
exports.Argument = Argument; | ||
var AllArguments = /** @class */ (function (_super) { | ||
__extends(AllArguments, _super); | ||
function AllArguments() { | ||
return _super.call(this, '{all arguments}', function () { return true; }) || this; | ||
} | ||
return AllArguments; | ||
}(Argument)); | ||
exports.AllArguments = AllArguments; | ||
var Arg = /** @class */ (function () { | ||
function Arg() { | ||
} | ||
Arg.all = function () { | ||
return new AllArguments(); | ||
}; | ||
Arg.any = function (type) { | ||
var description = !type ? '{any arg}' : '{arg matching ' + type + '}'; | ||
return new Argument(description, function (x) { | ||
if (typeof type === 'string') | ||
if (!type) | ||
return true; | ||
if (typeof type === 'undefined') | ||
if (typeof x === 'undefined') | ||
return true; | ||
@@ -31,19 +72,2 @@ if (type === 'array') | ||
exports.Arg = Arg; | ||
var Argument = /** @class */ (function () { | ||
function Argument(description, matchingFunction) { | ||
this.description = description; | ||
this.matchingFunction = matchingFunction; | ||
} | ||
Argument.prototype.matches = function (arg) { | ||
return this.matchingFunction(arg); | ||
}; | ||
Argument.prototype.toString = function () { | ||
return this.description; | ||
}; | ||
Argument.prototype.inspect = function () { | ||
return this.description; | ||
}; | ||
return Argument; | ||
}()); | ||
exports.Argument = Argument; | ||
//# sourceMappingURL=Arguments.js.map |
@@ -8,2 +8,3 @@ export declare abstract class ProxyPropertyContextBase { | ||
type: 'object'; | ||
mimicks: Function; | ||
returnValues: any[]; | ||
@@ -21,2 +22,3 @@ constructor(); | ||
returnValues: any[]; | ||
mimicks: Function; | ||
constructor(); | ||
@@ -44,3 +46,3 @@ } | ||
getLastCall(): ProxyCallRecord; | ||
addActualPropertyCall(): ProxyCallRecord; | ||
addActualPropertyCall(): void; | ||
} | ||
@@ -47,0 +49,0 @@ export declare class ProxyCallRecord { |
@@ -17,2 +17,3 @@ "use strict"; | ||
var Utilities_1 = require("./Utilities"); | ||
var Arguments_1 = require("./Arguments"); | ||
var ProxyPropertyContextBase = /** @class */ (function () { | ||
@@ -100,2 +101,6 @@ function ProxyPropertyContextBase() { | ||
return false; | ||
var firstArg1 = args1[0]; | ||
var firstArg2 = args2[0]; | ||
if (firstArg1 instanceof Arguments_1.AllArguments || firstArg2 instanceof Arguments_1.AllArguments) | ||
return true; | ||
if (args1.length !== args2.length) | ||
@@ -132,9 +137,7 @@ return false; | ||
} | ||
if (existingCall) { | ||
existingCall.callCount++; | ||
return; | ||
if (!existingCall) { | ||
existingCall = new ProxyCallRecord(this.property); | ||
this.calls.actual.push(existingCall); | ||
} | ||
var newCall = new ProxyCallRecord(this.property); | ||
this.calls.actual.push(newCall); | ||
return newCall; | ||
existingCall.callCount++; | ||
}; | ||
@@ -141,0 +144,0 @@ return ProxyObjectContext; |
@@ -15,6 +15,5 @@ "use strict"; | ||
var propertyContext = objectContext.property; | ||
var existingCalls = objectContext.findActualMethodCalls(propertyContext.name, argumentsList); | ||
var existingCall = existingCalls[0]; | ||
if (propertyContext.type === 'function') { | ||
var existingCalls = objectContext.findActualMethodCalls(propertyContext.name, argumentsList); | ||
if (existingCalls.length === 0) | ||
return void 0; | ||
var expected = objectContext.calls.expected; | ||
@@ -25,8 +24,14 @@ if (expected && expected.callCount !== void 0) { | ||
_this.assertCallMatchCount('method', expected, existingCalls); | ||
return thisProxy; | ||
return void 0; | ||
} | ||
var existingCall = existingCalls[0]; | ||
if (existingCall) { | ||
existingCall.callCount++; | ||
if (existingCall.property.type === 'function') { | ||
var mimicks = existingCall.property.method.mimicks; | ||
if (mimicks) | ||
return mimicks.call.apply(mimicks, [_target].concat(argumentsList)); | ||
} | ||
} | ||
if (!existingCall) | ||
return propertyContext.method.returnValues[0]; | ||
existingCall.callCount++; | ||
return void 0; | ||
if (propertyContext.method.returnValues) | ||
@@ -63,4 +68,3 @@ return propertyContext.method.returnValues[existingCall.callCount - 1]; | ||
objectContext.property = newMethodPropertyContext; | ||
var call = objectContext.addActualPropertyCall(); | ||
call.callCount = 1; | ||
objectContext.addActualPropertyCall(); | ||
return true; | ||
@@ -84,3 +88,3 @@ }, | ||
if (property === 'returns') { | ||
if (currentPropertyContext.type === 'object') { | ||
var createReturnsFunction = function (context) { | ||
return function () { | ||
@@ -91,15 +95,24 @@ var args = []; | ||
} | ||
currentPropertyContext.returnValues = args; | ||
context.returnValues = args; | ||
context.mimicks = void 0; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
} | ||
if (currentPropertyContext.type === 'function') { | ||
return function () { | ||
var args = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
args[_i] = arguments[_i]; | ||
} | ||
currentPropertyContext.method.returnValues = args; | ||
}; | ||
if (currentPropertyContext.type === 'object') | ||
return createReturnsFunction(currentPropertyContext); | ||
if (currentPropertyContext.type === 'function') | ||
return createReturnsFunction(currentPropertyContext.method); | ||
} | ||
if (property === 'mimicks') { | ||
var createMimicksFunction = function (context) { | ||
return function (value) { | ||
context.returnValues = void 0; | ||
context.mimicks = value; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
}; | ||
if (currentPropertyContext.type === 'object') | ||
return createMimicksFunction(currentPropertyContext); | ||
if (currentPropertyContext.type === 'function') { | ||
return createMimicksFunction(currentPropertyContext.method); | ||
} | ||
@@ -127,5 +140,8 @@ } | ||
existingCall.callCount++; | ||
if (!existingCallProperty.returnValues) | ||
return void 0; | ||
return existingCallProperty.returnValues[existingCall.callCount - 1]; | ||
if (existingCallProperty.returnValues) | ||
return existingCallProperty.returnValues[existingCall.callCount - 1]; | ||
var mimicks = existingCallProperty.mimicks; | ||
if (mimicks) | ||
return mimicks(); | ||
return void 0; | ||
} | ||
@@ -137,4 +153,3 @@ var newPropertyContext = new Context_1.ProxyPropertyContext(); | ||
objectContext.property = newPropertyContext; | ||
var call = objectContext.addActualPropertyCall(); | ||
call.callCount++; | ||
objectContext.addActualPropertyCall(); | ||
return thisProxy; | ||
@@ -141,0 +156,0 @@ } |
@@ -1,12 +0,21 @@ | ||
export declare type FunctionSubstitute<F extends any[], T> = (...args: F) => (T & MockObjectMixin<T>); | ||
export declare type PropertySubstitute<T> = T & Partial<MockObjectMixin<T>>; | ||
declare type MockObjectMixin<T> = { | ||
returns: (...args: T[]) => void; | ||
import { AllArguments } from "./Arguments"; | ||
export declare type NoArgumentFunctionSubstitute<TReturnType> = (() => (TReturnType & NoArgumentMockObjectMixin<TReturnType>)); | ||
export declare type FunctionSubstitute<TArguments extends any[], TReturnType> = ((...args: TArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>)) & ((allArguments: AllArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>)); | ||
export declare type PropertySubstitute<TReturnType> = TReturnType & Partial<NoArgumentMockObjectMixin<TReturnType>>; | ||
declare type BaseMockObjectMixin<TReturnType> = { | ||
returns: (...args: TReturnType[]) => void; | ||
}; | ||
declare type NoArgumentMockObjectMixin<TReturnType> = BaseMockObjectMixin<TReturnType> & { | ||
mimicks: (func: () => TReturnType) => void; | ||
}; | ||
declare type MockObjectMixin<TArguments extends any[], TReturnType> = BaseMockObjectMixin<TReturnType> & { | ||
mimicks: (func: (...args: TArguments) => TReturnType) => void; | ||
}; | ||
export declare type ObjectSubstitute<T extends Object> = ObjectSubstituteTransformation<T> & { | ||
received(amount?: number): T; | ||
mimick(instance: T): void; | ||
}; | ||
declare type ObjectSubstituteTransformation<T extends Object> = { | ||
[P in keyof T]: T[P] extends (...args: infer F) => infer R ? FunctionSubstitute<F, R> : PropertySubstitute<T[P]>; | ||
[P in keyof T]: T[P] extends () => infer R ? NoArgumentFunctionSubstitute<R> : T[P] extends (...args: infer F) => infer R ? FunctionSubstitute<F, R> : PropertySubstitute<T[P]>; | ||
}; | ||
export {}; |
@@ -26,2 +26,4 @@ "use strict"; | ||
function areArgumentsEqual(a, b) { | ||
if (a instanceof Arguments_1.AllArguments || b instanceof Arguments_1.AllArguments) | ||
return true; | ||
if (a instanceof Arguments_1.Argument && b instanceof Arguments_1.Argument) | ||
@@ -35,6 +37,2 @@ return a.matches(b) && b.matches(a); | ||
return true; | ||
if ((!a || !b) && a !== b) | ||
return false; | ||
if (Array.isArray(a) !== Array.isArray(b)) | ||
return false; | ||
return a === b; | ||
@@ -41,0 +39,0 @@ } |
{ | ||
"name": "@fluffy-spoon/substitute", | ||
"version": "1.0.52", | ||
"version": "1.0.53", | ||
"description": "An NSubstitute port to TypeScript called substitute.js.", | ||
@@ -5,0 +5,0 @@ "main": "dist/src/Index.js", |
@@ -15,2 +15,4 @@ [`@fluffy-spoon/substitute`](https://www.npmjs.com/package/@fluffy-spoon/substitute) is a TypeScript port of [NSubstitute](http://nsubstitute.github.io), which aims to provide a much more fluent mocking opportunity for strong-typed languages. | ||
add(a: number, b: number): number; | ||
subtract(a: number, b: number): number; | ||
divide(a: number, b: number): number; | ||
} | ||
@@ -52,6 +54,7 @@ | ||
### Matching specific arguments | ||
```typescript | ||
import { Arg } from '@fluffy-spoon/substitute'; | ||
//ignoring arguments | ||
//ignoring first argument | ||
calculator.add(Arg.any(), 2).returns(10); | ||
@@ -65,2 +68,60 @@ console.log(calculator.add(1337, 3)); //prints undefined since second argument doesn't match | ||
### Ignoring all arguments | ||
```typescript | ||
//ignoring all arguments | ||
calculator.add(Arg.all()).returns(10); | ||
console.log(calculator.add(1, 3)); //prints 10 | ||
console.log(calculator.add(5, 2)); //prints 10 | ||
``` | ||
### Match order | ||
The order of argument matchers matters. The first matcher that matches will always be used. Below are two examples. | ||
```typescript | ||
calculator.add(Arg.all()).returns(10); | ||
calculator.add(1, 3).returns(1337); | ||
console.log(calculator.add(1, 3)); //prints 10 | ||
console.log(calculator.add(5, 2)); //prints 10 | ||
``` | ||
```typescript | ||
calculator.add(1, 3).returns(1337); | ||
calculator.add(Arg.all()).returns(10); | ||
console.log(calculator.add(1, 3)); //prints 1337 | ||
console.log(calculator.add(5, 2)); //prints 10 | ||
``` | ||
## Partial mocks | ||
With partial mocks you always start with a true substitute where everything is mocked and then opt-out of substitutions in certain scenarios. | ||
```typescript | ||
import { Substitute, Arg } from '@fluffy-spoon/substitute'; | ||
class RealCalculator implements Calculator { | ||
add(a: number, b: number) => a + b; | ||
subtract(a: number, b: number) => a - b; | ||
} | ||
var realCalculator = new RealCalculator(); | ||
var fakeCalculator = Substitute.for<Calculator>(); | ||
//let the subtract method always use the real method | ||
fakeCalculator.subtract(Arg.all()).mimicks(realCalculator.subtract); | ||
console.log(fakeCalculator.subtract(20, 10)); //prints 10 | ||
console.log(fakeCalculator.subtract(1, 2)); //prints 10 | ||
//for the add method, we only use the real method when the first arg is less than 10 | ||
//else, we always return 1337 | ||
fakeCalculator.add(Arg.is(x < 10), Arg.any()).mimicks(realCalculator.add); | ||
fakeCalculator.add(Arg.is(x >= 10), Arg.any()).returns(1337); | ||
console.log(fakeCalculator.add(5, 100)); //prints 105 via real method | ||
console.log(fakeCalculator.add(210, 7)); //prints 1337 via fake method | ||
//for the divide method, we only use the real method for explicit arguments | ||
fakeCalculator.divide(10, 2).mimicks(realCalculator.divide); | ||
fakeCalculator.divide(Arg.all()).returns(1338); | ||
console.log(fakeCalculator.divide(10, 5)); //prints 5 | ||
console.log(fakeCalculator.divide(9, 5)); //prints 1338 | ||
``` | ||
# Benefits over other mocking libraries | ||
@@ -67,0 +128,0 @@ - Easier-to-understand fluent syntax. |
@@ -20,9 +20,11 @@ import test from 'ava'; | ||
foo(): void { | ||
console.log('stuff'); | ||
foo() { | ||
return 'stuff'; | ||
} | ||
} | ||
let instance: Example; | ||
let substitute: ObjectSubstitute<Example>; | ||
test.beforeEach(() => { | ||
instance = new Example(); | ||
substitute = Substitute.for<Example>(); | ||
@@ -32,7 +34,59 @@ }); | ||
test('are arguments equal', t => { | ||
t.true(areArgumentsEqual(Arg.any(), 'hi')); | ||
t.true(areArgumentsEqual(Arg.any('array'), ['foo', 'bar'])); | ||
t.false(areArgumentsEqual(['foo', 'bar'], ['foo', 'bar'])); | ||
t.false(areArgumentsEqual(Arg.any('array'), 1337)); | ||
}); | ||
t.true(areArgumentsEqual(Arg.any('array'), ['foo', 'bar'])); | ||
test('class method returns with placeholder args', t => { | ||
substitute.c(Arg.any(), "there").returns("blah", "haha"); | ||
t.deepEqual(substitute.c("hi", "there"), 'blah'); | ||
t.deepEqual(substitute.c("hi", "the1re"), void 0); | ||
t.deepEqual(substitute.c("his", "there"), 'haha'); | ||
t.deepEqual(substitute.c("his", "there"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
}); | ||
test('partial mocks using function mimicks with specific args', t => { | ||
substitute.c('a', 'b').mimicks(instance.c); | ||
t.deepEqual(substitute.c('c', 'b'), void 0); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
test('class method returns with specific args', t => { | ||
substitute.c("hi", "there").returns("blah", "haha"); | ||
t.deepEqual(substitute.c("hi", "there"), 'blah'); | ||
t.deepEqual(substitute.c("hi", "the1re"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), 'haha'); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
}); | ||
test('class string field get received', t => { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(() => substitute.received(3).a); | ||
t.notThrows(() => substitute.received().a); | ||
t.notThrows(() => substitute.received(4).a); | ||
}); | ||
test('partial mocks using function mimicks with all args', t => { | ||
substitute.c(Arg.all()).mimicks(instance.c); | ||
t.deepEqual(substitute.c('a', 'b'), 'hello a world (b)'); | ||
}); | ||
test('partial mocks using property instance mimicks', t => { | ||
substitute.d.mimicks(() => instance.d); | ||
t.deepEqual(substitute.d, 1337); | ||
}); | ||
test('class void returns', t => { | ||
@@ -72,23 +126,2 @@ substitute.foo().returns(void 0, null); | ||
test('class string field get received', t => { | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
void substitute.a; | ||
t.throws(() => substitute.received(3).a); | ||
t.notThrows(() => substitute.received().a); | ||
t.notThrows(() => substitute.received(4).a); | ||
}); | ||
test('class method returns', t => { | ||
substitute.c("hi", "there").returns("blah", "haha"); | ||
t.deepEqual(substitute.c("hi", "there"), 'blah'); | ||
t.deepEqual(substitute.c("hi", "the1re"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), 'haha'); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
t.deepEqual(substitute.c("hi", "there"), void 0); | ||
}); | ||
test('class method received', t => { | ||
@@ -95,0 +128,0 @@ void substitute.c("hi", "there"); |
@@ -0,2 +1,32 @@ | ||
export class Argument<T> { | ||
constructor( | ||
private description: string, | ||
private matchingFunction: (arg: T) => boolean | ||
) { | ||
} | ||
matches(arg: T) { | ||
return this.matchingFunction(arg); | ||
} | ||
toString() { | ||
return this.description; | ||
} | ||
inspect() { | ||
return this.description; | ||
} | ||
} | ||
export class AllArguments extends Argument<any> { | ||
constructor() { | ||
super('{all arguments}', () => true); | ||
} | ||
} | ||
export class Arg { | ||
static all() { | ||
return new AllArguments(); | ||
} | ||
static any() | ||
@@ -12,6 +42,6 @@ static any<T extends 'string'>(type: T): Argument<string> & string | ||
return new Argument<any>(description, x => { | ||
if(typeof type === 'string') | ||
if(!type) | ||
return true; | ||
if(typeof type === 'undefined') | ||
if(typeof x === 'undefined') | ||
return true; | ||
@@ -39,22 +69,2 @@ | ||
} | ||
} | ||
export class Argument<T> { | ||
constructor( | ||
private description: string, | ||
private matchingFunction: (arg: T) => boolean | ||
) { | ||
} | ||
matches(arg: T) { | ||
return this.matchingFunction(arg); | ||
} | ||
toString() { | ||
return this.description; | ||
} | ||
inspect() { | ||
return this.description; | ||
} | ||
} |
import { areArgumentsEqual } from "./Utilities"; | ||
import { ENGINE_METHOD_DIGESTS } from "constants"; | ||
import { AllArguments } from "./Arguments"; | ||
@@ -17,2 +17,4 @@ export abstract class ProxyPropertyContextBase { | ||
type: 'object'; | ||
mimicks: Function; | ||
returnValues: any[]; | ||
@@ -48,2 +50,3 @@ | ||
returnValues: any[]; | ||
mimicks: Function; | ||
@@ -112,2 +115,7 @@ constructor() { | ||
const firstArg1 = args1[0]; | ||
const firstArg2 = args2[0]; | ||
if(firstArg1 instanceof AllArguments || firstArg2 instanceof AllArguments) | ||
return true; | ||
if(args1.length !== args2.length) | ||
@@ -120,3 +128,3 @@ return false; | ||
if(!areArgumentsEqual(arg1, arg2)) | ||
if(!areArgumentsEqual(arg1, arg2)) | ||
return false; | ||
@@ -150,11 +158,8 @@ } | ||
if(existingCall) { | ||
existingCall.callCount++; | ||
return; | ||
if(!existingCall) { | ||
existingCall = new ProxyCallRecord(this.property); | ||
this.calls.actual.push(existingCall); | ||
} | ||
const newCall = new ProxyCallRecord(this.property); | ||
this.calls.actual.push(newCall); | ||
return newCall; | ||
existingCall.callCount++; | ||
} | ||
@@ -161,0 +166,0 @@ } |
@@ -6,2 +6,3 @@ import { ObjectSubstitute } from "./Transformations"; | ||
export class Substitute { | ||
static for<T>(): ObjectSubstitute<T> { | ||
@@ -14,7 +15,7 @@ const objectContext = new ProxyObjectContext(); | ||
const propertyContext = objectContext.property; | ||
const existingCalls = objectContext.findActualMethodCalls(propertyContext.name, argumentsList); | ||
const existingCall = existingCalls[0]; | ||
if (propertyContext.type === 'function') { | ||
let existingCalls = objectContext.findActualMethodCalls(propertyContext.name, argumentsList); | ||
if (existingCalls.length === 0) | ||
return void 0; | ||
const expected = objectContext.calls.expected; | ||
@@ -26,11 +27,18 @@ if(expected && expected.callCount !== void 0) { | ||
this.assertCallMatchCount('method', expected, existingCalls); | ||
return thisProxy; | ||
return void 0; | ||
} | ||
const existingCall = existingCalls[0]; | ||
if(existingCall) { | ||
existingCall.callCount++; | ||
if(existingCall.property.type === 'function') { | ||
const mimicks = existingCall.property.method.mimicks; | ||
if(mimicks) | ||
return mimicks.call(_target, ...argumentsList); | ||
} | ||
} | ||
if(!existingCall) | ||
return propertyContext.method.returnValues[0]; | ||
return void 0; | ||
existingCall.callCount++; | ||
if(propertyContext.method.returnValues) | ||
@@ -77,4 +85,3 @@ return propertyContext.method.returnValues[existingCall.callCount - 1]; | ||
const call = objectContext.addActualPropertyCall(); | ||
call.callCount = 1; | ||
objectContext.addActualPropertyCall(); | ||
@@ -105,14 +112,33 @@ return true; | ||
if (property === 'returns') { | ||
if (currentPropertyContext.type === 'object') { | ||
const createReturnsFunction = (context: {returnValues, mimicks}) => { | ||
return (...args: any[]) => { | ||
currentPropertyContext.returnValues = args; | ||
context.returnValues = args; | ||
context.mimicks = void 0; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
} | ||
}; | ||
if (currentPropertyContext.type === 'function') { | ||
return (...args: any[]) => { | ||
currentPropertyContext.method.returnValues = args; | ||
if (currentPropertyContext.type === 'object') | ||
return createReturnsFunction(currentPropertyContext); | ||
if (currentPropertyContext.type === 'function') | ||
return createReturnsFunction(currentPropertyContext.method); | ||
} | ||
if(property === 'mimicks') { | ||
const createMimicksFunction = (context: {returnValues, mimicks}) => { | ||
return (value: Function) => { | ||
context.returnValues = void 0; | ||
context.mimicks = value; | ||
objectContext.getLastCall().callCount--; | ||
}; | ||
}; | ||
if(currentPropertyContext.type === 'object') | ||
return createMimicksFunction(currentPropertyContext); | ||
if(currentPropertyContext.type === 'function') { | ||
return createMimicksFunction(currentPropertyContext.method); | ||
} | ||
@@ -147,6 +173,10 @@ } | ||
if (!existingCallProperty.returnValues) | ||
return void 0; | ||
if (existingCallProperty.returnValues) | ||
return existingCallProperty.returnValues[existingCall.callCount - 1]; | ||
const mimicks = existingCallProperty.mimicks; | ||
if(mimicks) | ||
return mimicks(); | ||
return existingCallProperty.returnValues[existingCall.callCount - 1]; | ||
return void 0; | ||
} | ||
@@ -161,4 +191,3 @@ | ||
const call = objectContext.addActualPropertyCall(); | ||
call.callCount++; | ||
objectContext.addActualPropertyCall(); | ||
@@ -165,0 +194,0 @@ return thisProxy; |
@@ -1,11 +0,27 @@ | ||
export type FunctionSubstitute<F extends any[], T> = (...args: F) => (T & MockObjectMixin<T>) | ||
import { AllArguments } from "./Arguments"; | ||
export type PropertySubstitute<T> = T & Partial<MockObjectMixin<T>> | ||
export type NoArgumentFunctionSubstitute<TReturnType> = | ||
(() => (TReturnType & NoArgumentMockObjectMixin<TReturnType>)) | ||
type MockObjectMixin<T> = { | ||
returns: (...args: T[]) => void; | ||
export type FunctionSubstitute<TArguments extends any[], TReturnType> = | ||
((...args: TArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>)) & | ||
((allArguments: AllArguments) => (TReturnType & MockObjectMixin<TArguments, TReturnType>)) | ||
export type PropertySubstitute<TReturnType> = TReturnType & Partial<NoArgumentMockObjectMixin<TReturnType>> | ||
type BaseMockObjectMixin<TReturnType> = { | ||
returns: (...args: TReturnType[]) => void; | ||
} | ||
type NoArgumentMockObjectMixin<TReturnType> = BaseMockObjectMixin<TReturnType> & { | ||
mimicks: (func: () => TReturnType) => void; | ||
} | ||
type MockObjectMixin<TArguments extends any[], TReturnType> = BaseMockObjectMixin<TReturnType> & { | ||
mimicks: (func: (...args: TArguments) => TReturnType) => void; | ||
} | ||
export type ObjectSubstitute<T extends Object> = ObjectSubstituteTransformation<T> & { | ||
received(amount?: number): T; | ||
mimick(instance: T): void; | ||
} | ||
@@ -15,4 +31,5 @@ | ||
[P in keyof T]: | ||
T[P] extends () => infer R ? NoArgumentFunctionSubstitute<R> : | ||
T[P] extends (...args: infer F) => infer R ? FunctionSubstitute<F, R> : | ||
PropertySubstitute<T[P]>; | ||
} |
import { ProxyCallRecord } from "./Context"; | ||
import { Argument } from "./Arguments"; | ||
import { Argument, AllArguments } from "./Arguments"; | ||
@@ -27,2 +27,5 @@ export function stringifyArguments(args: any[]) { | ||
export function areArgumentsEqual(a: any, b: any) { | ||
if(a instanceof AllArguments || b instanceof AllArguments) | ||
return true; | ||
if(a instanceof Argument && b instanceof Argument) | ||
@@ -40,9 +43,3 @@ return a.matches(b) && b.matches(a); | ||
if ((!a || !b) && a !== b) | ||
return false; | ||
if (Array.isArray(a) !== Array.isArray(b)) | ||
return false; | ||
return a === b; | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
108604
1276
130