Skip to content

Commit 056337a

Browse files
Arguments type enhancements and .not implementation (#108)
* add generics correctly to Arg.is and Arg.all * rework arguments and implement .not * add some arguments test * upgrade dependencies * add Arguments spec * fix types to support ts 3.9 * add documentation for inverse matchers * simplified example
1 parent b233046 commit 056337a

File tree

7 files changed

+274
-106
lines changed

7 files changed

+274
-106
lines changed

README.md

Lines changed: 65 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,61 +25,61 @@ interface Calculator {
2525
isEnabled: boolean;
2626
}
2727

28-
//Create:
29-
var calculator = Substitute.for<Calculator>();
28+
// Create:
29+
const calculator = Substitute.for<Calculator>();
3030

31-
//Set a return value:
31+
// Set a return value:
3232
calculator.add(1, 2).returns(3);
3333

34-
//Check received calls:
34+
// Check received calls:
3535
calculator.received().add(1, Arg.any());
3636
calculator.didNotReceive().add(2, 2);
3737
```
3838

3939
## Creating a mock
40-
`var calculator = Substitute.for<Calculator>();`
40+
`const calculator = Substitute.for<Calculator>();`
4141

4242
## Setting return types
4343
See the example below. The same syntax also applies to properties and fields.
4444

4545
```typescript
46-
//single return type
46+
// single return type
4747
calculator.add(1, 2).returns(4);
48-
console.log(calculator.add(1, 2)); //prints 4
49-
console.log(calculator.add(1, 2)); //prints undefined
48+
console.log(calculator.add(1, 2)); // prints 4
49+
console.log(calculator.add(1, 2)); // prints undefined
5050

51-
//multiple return types in sequence
51+
// multiple return types in sequence
5252
calculator.add(1, 2).returns(3, 7, 9);
53-
console.log(calculator.add(1, 2)); //prints 3
54-
console.log(calculator.add(1, 2)); //prints 7
55-
console.log(calculator.add(1, 2)); //prints 9
56-
console.log(calculator.add(1, 2)); //prints undefined
53+
console.log(calculator.add(1, 2)); // prints 3
54+
console.log(calculator.add(1, 2)); // prints 7
55+
console.log(calculator.add(1, 2)); // prints 9
56+
console.log(calculator.add(1, 2)); // prints undefined
5757
```
5858

5959
## Working with promises
6060
When working with promises you can also use `resolves()` and `rejects()` to return a promise.
6161

6262
```typescript
6363
calculator.heavyOperation(1, 2).resolves(4);
64-
//same as calculator.heavyOperation(1, 2).returns(Promise.resolve(4));
65-
console.log(await calculator.heavyOperation(1, 2)); //prints 4
64+
// same as calculator.heavyOperation(1, 2).returns(Promise.resolve(4));
65+
console.log(await calculator.heavyOperation(1, 2)); // prints 4
6666
```
6767

6868
```typescript
6969
calculator.heavyOperation(1, 2).rejects(new Error());
70-
//same as calculator.heavyOperation(1, 2).returns(Promise.reject(new Error()));
71-
console.log(await calculator.heavyOperation(1, 2)); //throws Error
70+
// same as calculator.heavyOperation(1, 2).returns(Promise.reject(new Error()));
71+
console.log(await calculator.heavyOperation(1, 2)); // throws Error
7272
```
7373

7474
## Verifying calls
7575
```typescript
7676
calculator.enabled = true;
77-
var foo = calculator.add(1, 2);
77+
const foo = calculator.add(1, 2);
7878

79-
//verify call to add(1, 2)
79+
// verify call to add(1, 2)
8080
calculator.received().add(1, 2);
8181

82-
//verify property set to "true"
82+
// verify property set to "true"
8383
calculator.received().enabled = true;
8484
```
8585

@@ -90,21 +90,38 @@ There are several ways of matching arguments. The examples below also applies to
9090
```typescript
9191
import { Arg } from '@fluffy-spoon/substitute';
9292

93-
//ignoring first argument
93+
// ignoring first argument
9494
calculator.add(Arg.any(), 2).returns(10);
95-
console.log(calculator.add(1337, 3)); //prints undefined since second argument doesn't match
96-
console.log(calculator.add(1337, 2)); //prints 10 since second argument matches
95+
console.log(calculator.add(1337, 3)); // prints undefined since second argument doesn't match
96+
console.log(calculator.add(1337, 2)); // prints 10 since second argument matches
9797

98-
//received call with first arg 1 and second arg less than 0
98+
// received call with first arg 1 and second arg less than 0
9999
calculator.received().add(1, Arg.is(x => x < 0));
100100
```
101101

102+
### Generic and inverse matchers
103+
```typescript
104+
import { Arg } from '@fluffy-spoon/substitute';
105+
106+
const equalToZero = (x: number) => x === 0;
107+
108+
// first argument will match any number
109+
// second argument will match a number that is not '0'
110+
calculator.divide(Arg.any('number'), Arg.is.not(equalToZero)).returns(10);
111+
console.log(calculator.divide(100, 10)); // prints 10
112+
113+
const argIsNotZero = Arg.is.not(equalToZero);
114+
calculator.received(1).divide(argIsNotZero, argIsNotZero);
115+
```
116+
117+
> #### Note: `Arg.is()` will automatically infer the type of the argument it's replacing
118+
102119
### Ignoring all arguments
103120
```typescript
104-
//ignoring all arguments
121+
// ignoring all arguments
105122
calculator.add(Arg.all()).returns(10);
106-
console.log(calculator.add(1, 3)); //prints 10
107-
console.log(calculator.add(5, 2)); //prints 10
123+
console.log(calculator.add(1, 3)); // prints 10
124+
console.log(calculator.add(5, 2)); // prints 10
108125
```
109126

110127
### Match order
@@ -113,15 +130,15 @@ The order of argument matchers matters. The first matcher that matches will alwa
113130
```typescript
114131
calculator.add(Arg.all()).returns(10);
115132
calculator.add(1, 3).returns(1337);
116-
console.log(calculator.add(1, 3)); //prints 10
117-
console.log(calculator.add(5, 2)); //prints 10
133+
console.log(calculator.add(1, 3)); // prints 10
134+
console.log(calculator.add(5, 2)); // prints 10
118135
```
119136

120137
```typescript
121138
calculator.add(1, 3).returns(1337);
122139
calculator.add(Arg.all()).returns(10);
123-
console.log(calculator.add(1, 3)); //prints 1337
124-
console.log(calculator.add(5, 2)); //prints 10
140+
console.log(calculator.add(1, 3)); // prints 1337
141+
console.log(calculator.add(5, 2)); // prints 10
125142
```
126143

127144
## Partial mocks
@@ -136,26 +153,26 @@ class RealCalculator implements Calculator {
136153
divide(a: number, b: number) => a / b;
137154
}
138155

139-
var realCalculator = new RealCalculator();
140-
var fakeCalculator = Substitute.for<Calculator>();
156+
const realCalculator = new RealCalculator();
157+
const fakeCalculator = Substitute.for<Calculator>();
141158

142-
//let the subtract method always use the real method
159+
// let the subtract method always use the real method
143160
fakeCalculator.subtract(Arg.all()).mimicks(realCalculator.subtract);
144-
console.log(fakeCalculator.subtract(20, 10)); //prints 10
145-
console.log(fakeCalculator.subtract(1, 2)); //prints -1
161+
console.log(fakeCalculator.subtract(20, 10)); // prints 10
162+
console.log(fakeCalculator.subtract(1, 2)); // prints -1
146163

147-
//for the add method, we only use the real method when the first arg is less than 10
148-
//else, we always return 1337
164+
// for the add method, we only use the real method when the first arg is less than 10
165+
// else, we always return 1337
149166
fakeCalculator.add(Arg.is(x < 10), Arg.any()).mimicks(realCalculator.add);
150167
fakeCalculator.add(Arg.is(x >= 10), Arg.any()).returns(1337);
151-
console.log(fakeCalculator.add(5, 100)); //prints 105 via real method
152-
console.log(fakeCalculator.add(210, 7)); //prints 1337 via fake method
168+
console.log(fakeCalculator.add(5, 100)); // prints 105 via real method
169+
console.log(fakeCalculator.add(210, 7)); // prints 1337 via fake method
153170

154-
//for the divide method, we only use the real method for explicit arguments
171+
// for the divide method, we only use the real method for explicit arguments
155172
fakeCalculator.divide(10, 2).mimicks(realCalculator.divide);
156173
fakeCalculator.divide(Arg.all()).returns(1338);
157-
console.log(fakeCalculator.divide(10, 5)); //prints 5
158-
console.log(fakeCalculator.divide(9, 5)); //prints 1338
174+
console.log(fakeCalculator.divide(10, 5)); // prints 5
175+
console.log(fakeCalculator.divide(9, 5)); // prints 1338
159176
```
160177

161178
## Throwing exceptions
@@ -196,15 +213,15 @@ class Example {
196213
}
197214
}
198215

199-
var fake = Substitute.for<Example>();
216+
const fake = Substitute.for<Example>();
200217

201-
//BAD: this would have called substitute.js' "received" method.
202-
//fake.received(2);
218+
// BAD: this would have called substitute.js' "received" method.
219+
// fake.received(2);
203220

204-
//GOOD: we now call the "received" method we have defined in the class above.
221+
// GOOD: we now call the "received" method we have defined in the class above.
205222
Substitute.disableFor(fake).received(1337);
206223

207-
//now we can assert that we received a call to the "received" method.
224+
// now we can assert that we received a call to the "received" method.
208225
fake.received().received(1337);
209226
```
210227

package-lock.json

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

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"devDependencies": {
3232
"@ava/typescript": "^1.1.0",
3333
"ava": "^3.8.2",
34-
"typescript": "^3.0.0"
34+
"typescript": "^3.9.2"
3535
},
3636
"ava": {
3737
"typescript": {

spec/Arguments.spec.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import test from 'ava';
2+
import { Arg } from '../src';
3+
import { Argument } from 'src/Arguments';
4+
5+
const testObject = { "foo": "bar" };
6+
const testArray = ["a", 1, true];
7+
8+
const parent = {} as any;
9+
parent.child = parent;
10+
const root = {} as any;
11+
root.path = { to: { nested: root } };
12+
const testFunc = () => { };
13+
14+
test('should match any argument(s) using Arg.all', t => {
15+
t.true(Arg.all().matches([]));
16+
t.true(Arg.all().matches([0]));
17+
t.true(Arg.all().matches([1]));
18+
t.true(Arg.all().matches(['string']));
19+
t.true(Arg.all().matches([true]));
20+
t.true(Arg.all().matches([false]));
21+
t.true(Arg.all().matches(null));
22+
t.true(Arg.all().matches(undefined));
23+
t.true(Arg.all().matches([1, 2]));
24+
t.true(Arg.all().matches(['string1', 'string2']));
25+
})
26+
27+
test('should match any argument using Arg.any', t => {
28+
t.true(Arg.any().matches('hi'));
29+
t.true(Arg.any().matches(1));
30+
t.true(Arg.any().matches(0));
31+
t.true(Arg.any().matches(false));
32+
t.true(Arg.any().matches(true));
33+
t.true(Arg.any().matches(null));
34+
t.true(Arg.any().matches(undefined));
35+
t.true(Arg.any().matches(testObject));
36+
t.true(Arg.any().matches(testArray));
37+
t.true(Arg.any().matches(testFunc));
38+
t.true(Arg.any().matches());
39+
t.true(Arg.any().matches(parent));
40+
t.true(Arg.any().matches(root));
41+
t.true(Arg.any().matches(parent));
42+
t.true(Arg.any().matches(root));
43+
});
44+
45+
test('should not match any argument using Arg.any.not', t => {
46+
t.false(Arg.any.not().matches('hi'));
47+
t.false(Arg.any.not().matches(1));
48+
t.false(Arg.any.not().matches(0));
49+
t.false(Arg.any.not().matches(false));
50+
t.false(Arg.any.not().matches(true));
51+
t.false(Arg.any.not().matches(null));
52+
t.false(Arg.any.not().matches(undefined));
53+
t.false(Arg.any.not().matches(testObject));
54+
t.false(Arg.any.not().matches(testArray));
55+
t.false(Arg.any.not().matches(testFunc));
56+
t.false(Arg.any.not().matches());
57+
t.false(Arg.any.not().matches(parent));
58+
t.false(Arg.any.not().matches(root));
59+
t.false(Arg.any.not().matches(parent));
60+
t.false(Arg.any.not().matches(root));
61+
});
62+
63+
test('should match the type of the argument using Arg.any', t => {
64+
t.true(Arg.any('string').matches('foo'));
65+
t.true(Arg.any('number').matches(1));
66+
t.true(Arg.any('boolean').matches(true));
67+
t.true(Arg.any('symbol').matches(Symbol()));
68+
t.true((<Argument<unknown>>Arg.any('undefined')).matches(undefined));
69+
t.true(Arg.any('object').matches(testObject));
70+
t.true(Arg.any('array').matches(testArray));
71+
t.true(Arg.any('function').matches(testFunc));
72+
t.true(Arg.any('object').matches(parent));
73+
t.true(Arg.any('object').matches(root));
74+
75+
t.false((<Argument<unknown>>Arg.any('string')).matches(1));
76+
t.false((<Argument<unknown>>Arg.any('number')).matches('string'));
77+
t.false(Arg.any('boolean').matches(null));
78+
t.false((<Argument<unknown>>Arg.any('object')).matches('foo'));
79+
t.false((<Argument<unknown>>Arg.any('array')).matches('bar'));
80+
t.false((<Argument<unknown>>Arg.any('function')).matches('foo'));
81+
});
82+
83+
84+
test('should not match the type of the argument using Arg.any.not', t => {
85+
t.false(Arg.any.not('string').matches('123'));
86+
t.false(Arg.any.not('number').matches(123));
87+
t.false(Arg.any.not('boolean').matches(true));
88+
t.false(Arg.any.not('symbol').matches(Symbol()));
89+
t.false((<Argument<unknown>>Arg.any.not('undefined')).matches(undefined));
90+
t.false(Arg.any.not('object').matches(testObject));
91+
t.false(Arg.any.not('array').matches(testArray));
92+
t.false(Arg.any.not('function').matches(testFunc));
93+
t.false(Arg.any.not('object').matches(parent));
94+
t.false(Arg.any.not('object').matches(root));
95+
});
96+
97+
test('should match the argument with the predicate function using Arg.is', t => {
98+
t.true(Arg.is<string>(x => x === 'foo').matches('foo'));
99+
t.true(Arg.is<number>(x => x % 2 == 0).matches(4));
100+
101+
t.false(Arg.is<string>(x => x === 'foo').matches('bar'));
102+
t.false(Arg.is<number>(x => x % 2 == 0).matches(3));
103+
});
104+
105+
test('should not match the argument with the predicate function using Arg.is.not', t => {
106+
t.false(Arg.is.not<string>(x => x === 'foo').matches('foo'));
107+
t.false(Arg.is.not<number>(x => x % 2 == 0).matches(4));
108+
109+
t.true(Arg.is.not<string>(x => x === 'foo').matches('bar'));
110+
t.true(Arg.is.not<number>(x => x % 2 == 0).matches(3));
111+
});

0 commit comments

Comments
 (0)