Skip to content

Commit 48e4113

Browse files
authored
mimicking (#4)
* mimicking
1 parent 8c511a1 commit 48e4113

24 files changed

+485
-253
lines changed

README.md

Lines changed: 129 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,129 @@
1-
[`@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.
2-
3-
# Installing
4-
`npm install @fluffy-spoon/substitute --save-dev`
5-
6-
# Requirements
7-
* `TypeScript^3.0.0`
8-
9-
# Usage
10-
```typescript
11-
import { Substitute, Arg } from '@fluffy-spoon/substitute';
12-
13-
interface Calculator {
14-
add(a: number, b: number): number;
15-
}
16-
17-
//Create:
18-
var calculator = Substitute.for<Calculator>();
19-
20-
//Set a return value:
21-
calculator.add(1, 2).returns(3);
22-
23-
//Check received calls:
24-
calculator.received().add(1, Arg.any());
25-
calculator.didNotReceive().add(2, 2);
26-
```
27-
28-
## Creating a mock
29-
`var calculator = Substitute.for<Calculator>();`
30-
31-
## Setting return types
32-
See the example below. The same syntax also applies to properties and fields.
33-
34-
```typescript
35-
//single return type
36-
calculator.add(1, 2).returns(4);
37-
console.log(calculator.add(1, 2)); //prints 4
38-
console.log(calculator.add(1, 2)); //prints undefined
39-
40-
//multiple return types in sequence
41-
calculator.add(1, 2).returns(3, 7, 9);
42-
console.log(calculator.add(1, 2)); //prints 3
43-
console.log(calculator.add(1, 2)); //prints 7
44-
console.log(calculator.add(1, 2)); //prints 9
45-
console.log(calculator.add(1, 2)); //prints undefined
46-
```
47-
48-
## Argument matchers
49-
There are several ways of matching arguments. The examples below also applies to properties and fields.
50-
51-
```typescript
52-
import { Arg } from '@fluffy-spoon/substitute';
53-
54-
//ignoring arguments
55-
calculator.add(Arg.any(), 2).returns(10);
56-
console.log(calculator.add(1337, 3)); //prints undefined since second argument doesn't match
57-
console.log(calculator.add(1337, 2)); //prints 10 since second argument matches
58-
59-
//received call with first arg 1 and second arg less than 0
60-
calculator.received().add(1, Arg.is(x => x < 0));
61-
```
62-
63-
# Benefits over other mocking libraries
64-
- Easier-to-understand fluent syntax.
65-
- No need to cast to `any` in certain places (for instance, when overriding read-only properties) due to the `myProperty.returns(...)` syntax.
66-
- Doesn't weigh much.
67-
- Produces very clean and descriptive error messages. Try it out - you'll love it.
68-
- Doesn't rely on object instances - you can produce a strong-typed fake from nothing, ensuring that everything is mocked.
1+
[`@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.
2+
3+
# Installing
4+
`npm install @fluffy-spoon/substitute --save-dev`
5+
6+
# Requirements
7+
* `TypeScript^3.0.0`
8+
9+
# Usage
10+
```typescript
11+
import { Substitute, Arg } from '@fluffy-spoon/substitute';
12+
13+
interface Calculator {
14+
add(a: number, b: number): number;
15+
subtract(a: number, b: number): number;
16+
divide(a: number, b: number): number;
17+
}
18+
19+
//Create:
20+
var calculator = Substitute.for<Calculator>();
21+
22+
//Set a return value:
23+
calculator.add(1, 2).returns(3);
24+
25+
//Check received calls:
26+
calculator.received().add(1, Arg.any());
27+
calculator.didNotReceive().add(2, 2);
28+
```
29+
30+
## Creating a mock
31+
`var calculator = Substitute.for<Calculator>();`
32+
33+
## Setting return types
34+
See the example below. The same syntax also applies to properties and fields.
35+
36+
```typescript
37+
//single return type
38+
calculator.add(1, 2).returns(4);
39+
console.log(calculator.add(1, 2)); //prints 4
40+
console.log(calculator.add(1, 2)); //prints undefined
41+
42+
//multiple return types in sequence
43+
calculator.add(1, 2).returns(3, 7, 9);
44+
console.log(calculator.add(1, 2)); //prints 3
45+
console.log(calculator.add(1, 2)); //prints 7
46+
console.log(calculator.add(1, 2)); //prints 9
47+
console.log(calculator.add(1, 2)); //prints undefined
48+
```
49+
50+
## Argument matchers
51+
There are several ways of matching arguments. The examples below also applies to properties and fields.
52+
53+
### Matching specific arguments
54+
```typescript
55+
import { Arg } from '@fluffy-spoon/substitute';
56+
57+
//ignoring first argument
58+
calculator.add(Arg.any(), 2).returns(10);
59+
console.log(calculator.add(1337, 3)); //prints undefined since second argument doesn't match
60+
console.log(calculator.add(1337, 2)); //prints 10 since second argument matches
61+
62+
//received call with first arg 1 and second arg less than 0
63+
calculator.received().add(1, Arg.is(x => x < 0));
64+
```
65+
66+
### Ignoring all arguments
67+
```typescript
68+
//ignoring all arguments
69+
calculator.add(Arg.all()).returns(10);
70+
console.log(calculator.add(1, 3)); //prints 10
71+
console.log(calculator.add(5, 2)); //prints 10
72+
```
73+
74+
### Match order
75+
The order of argument matchers matters. The first matcher that matches will always be used. Below are two examples.
76+
77+
```typescript
78+
calculator.add(Arg.all()).returns(10);
79+
calculator.add(1, 3).returns(1337);
80+
console.log(calculator.add(1, 3)); //prints 10
81+
console.log(calculator.add(5, 2)); //prints 10
82+
```
83+
84+
```typescript
85+
calculator.add(1, 3).returns(1337);
86+
calculator.add(Arg.all()).returns(10);
87+
console.log(calculator.add(1, 3)); //prints 1337
88+
console.log(calculator.add(5, 2)); //prints 10
89+
```
90+
91+
## Partial mocks
92+
With partial mocks you always start with a true substitute where everything is mocked and then opt-out of substitutions in certain scenarios.
93+
94+
```typescript
95+
import { Substitute, Arg } from '@fluffy-spoon/substitute';
96+
97+
class RealCalculator implements Calculator {
98+
add(a: number, b: number) => a + b;
99+
subtract(a: number, b: number) => a - b;
100+
}
101+
102+
var realCalculator = new RealCalculator();
103+
var fakeCalculator = Substitute.for<Calculator>();
104+
105+
//let the subtract method always use the real method
106+
fakeCalculator.subtract(Arg.all()).mimicks(realCalculator.subtract);
107+
console.log(fakeCalculator.subtract(20, 10)); //prints 10
108+
console.log(fakeCalculator.subtract(1, 2)); //prints 10
109+
110+
//for the add method, we only use the real method when the first arg is less than 10
111+
//else, we always return 1337
112+
fakeCalculator.add(Arg.is(x < 10), Arg.any()).mimicks(realCalculator.add);
113+
fakeCalculator.add(Arg.is(x >= 10), Arg.any()).returns(1337);
114+
console.log(fakeCalculator.add(5, 100)); //prints 105 via real method
115+
console.log(fakeCalculator.add(210, 7)); //prints 1337 via fake method
116+
117+
//for the divide method, we only use the real method for explicit arguments
118+
fakeCalculator.divide(10, 2).mimicks(realCalculator.divide);
119+
fakeCalculator.divide(Arg.all()).returns(1338);
120+
console.log(fakeCalculator.divide(10, 5)); //prints 5
121+
console.log(fakeCalculator.divide(9, 5)); //prints 1338
122+
```
123+
124+
# Benefits over other mocking libraries
125+
- Easier-to-understand fluent syntax.
126+
- No need to cast to `any` in certain places (for instance, when overriding read-only properties) due to the `myProperty.returns(...)` syntax.
127+
- Doesn't weigh much.
128+
- Produces very clean and descriptive error messages. Try it out - you'll love it.
129+
- Doesn't rely on object instances - you can produce a strong-typed fake from nothing, ensuring that everything is mocked.

dist/spec/index.test.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ export declare class Example {
33
c(arg1: string, arg2: string): string;
44
readonly d: number;
55
v: string;
6-
foo(): void;
6+
foo(): string;
77
}

dist/spec/index.test.js

Lines changed: 44 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/spec/index.test.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/src/Arguments.d.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1+
export declare class Argument<T> {
2+
private description;
3+
private matchingFunction;
4+
constructor(description: string, matchingFunction: (arg: T) => boolean);
5+
matches(arg: T): boolean;
6+
toString(): string;
7+
inspect(): string;
8+
}
9+
export declare class AllArguments extends Argument<any> {
10+
constructor();
11+
}
112
export declare class Arg {
13+
static all(): AllArguments;
214
static any(): any;
315
static any<T extends 'string'>(type: T): Argument<string> & string;
416
static any<T extends 'number'>(type: T): Argument<number> & number;
@@ -9,11 +21,3 @@ export declare class Arg {
921
static is<T>(predicate: (input: T) => boolean): Argument<T> & T;
1022
private static toStringify;
1123
}
12-
export declare class Argument<T> {
13-
private description;
14-
private matchingFunction;
15-
constructor(description: string, matchingFunction: (arg: T) => boolean);
16-
matches(arg: T): boolean;
17-
toString(): string;
18-
inspect(): string;
19-
}

0 commit comments

Comments
 (0)