Skip to content

Commit 125553d

Browse files
author
Mathias Lorenzen
committed
first version.
1 parent 4837388 commit 125553d

File tree

7 files changed

+197
-56
lines changed

7 files changed

+197
-56
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"typescript.tsdk": "node_modules/typescript/lib"
3+
}

README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,53 @@
1-
# FluffySpoon.JavaScript.Testing
1+
# FluffySpoon.JavaScript.Testing
2+
More concretely named `@fluffy-spoon/substitute` on NPM is a TypeScript port of [NSubstitute](http://nsubstitute.github.io), which aims to provide a much more fluent mocking opportunity for strong-typed languages.
3+
4+
## Requirements
5+
* `TypeScript^3.0.0`
6+
7+
## Usage
8+
Experience full strong-typing of your fakes all the way, and let the TypeScript compiler help with all the dirty work! In the usage example given below, the `exFake` instance is strong-typed all the way, and can be used naturally in a fluent interface!
9+
10+
```
11+
class Example {
12+
a = "1337";
13+
b = 1337;
14+
15+
c(arg1: string, arg2: string) {
16+
return "hello " + arg1 + " world (" + arg2 + ")";
17+
}
18+
19+
get d() {
20+
return 1337;
21+
}
22+
23+
set v(x) {
24+
console.log('define: ' + x);
25+
}
26+
}
27+
28+
var exFake = Substitute.for<Example>();
29+
30+
exFake.a.returns("foo", "bar");
31+
console.log(exFake.a); //prints "foo"
32+
console.log(exFake.a); //prints "bar"
33+
console.log(exFake.a); //prints undefined
34+
35+
exFake.b.returns(10, 30, 99);
36+
console.log(exFake.b); //prints 10
37+
console.log(exFake.b); //prints 30
38+
console.log(exFake.b); //prints 99
39+
console.log(exFake.b); //prints undefined
40+
41+
exFake.c("hi", "there").returns("blah", "haha", "oooh", "lala");
42+
console.log(exFake.c("hi", "there")); //prints "blah"
43+
console.log(exFake.c("hi", "there")); //prints "haha"
44+
console.log(exFake.c("hi", "the1re")); //prints undefined (since it doesn't match the parameters)
45+
console.log(exFake.c("hi", "there")); //prints "ooh"
46+
console.log(exFake.c("something", "there")); //prints undefined (since it doesn't match the parameters)
47+
48+
exFake.d.returns(9);
49+
console.log(exFake.d); //prints 9
50+
```
51+
52+
## But how?
53+
`@fluffy-spoon/substitute` works the same way that NSubstitute does, except that it uses the EcmaScript 6 `Proxy` class to produce the fakes. You can read more about how NSubstitute works to get inspired.

dist/index.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
var Substitute = /** @class */ (function () {
4+
function Substitute() {
5+
}
6+
Substitute.for = function () {
7+
var lastRecord;
8+
var createRecord = function () {
9+
lastRecord = {
10+
arguments: null,
11+
shouldReturn: [],
12+
proxy: null,
13+
currentReturnOffset: 0,
14+
property: null
15+
};
16+
return lastRecord;
17+
};
18+
var equals = function (a, b) {
19+
if ((!a || !b) && a !== b)
20+
return false;
21+
if (typeof a !== typeof b)
22+
return false;
23+
if (Array.isArray(a) !== Array.isArray(b))
24+
return false;
25+
if (Array.isArray(a) && Array.isArray(b)) {
26+
if (a.length !== b.length)
27+
return false;
28+
for (var i = 0; i < a.length; i++) {
29+
if (!equals(a[i], b[i]))
30+
return false;
31+
}
32+
return true;
33+
}
34+
return a === b;
35+
};
36+
var createProxy = function (r) {
37+
if (r === void 0) { r = null; }
38+
var localRecord = r;
39+
var thisProxy;
40+
return thisProxy = new Proxy(function () { }, {
41+
apply: function (_target, _thisArg, argumentsList) {
42+
if (localRecord.arguments) {
43+
if (!equals(localRecord.arguments, argumentsList))
44+
return localRecord.proxy || (localRecord.proxy = createProxy());
45+
return localRecord.shouldReturn[localRecord.currentReturnOffset++];
46+
}
47+
localRecord.arguments = argumentsList;
48+
return thisProxy;
49+
},
50+
get: function (target, property) {
51+
if (typeof property === 'symbol') {
52+
if (property === Symbol.toPrimitive)
53+
return function () { return void 0; };
54+
return void 0;
55+
}
56+
if (property === 'valueOf')
57+
return void 0;
58+
if (property === 'toString')
59+
return (target[property] || '').toString();
60+
if (property === 'inspect')
61+
return function () { return "{SubstituteJS fake}"; };
62+
if (property === 'constructor')
63+
return function () { return thisProxy; };
64+
if (property === 'returns')
65+
return function () {
66+
var args = [];
67+
for (var _i = 0; _i < arguments.length; _i++) {
68+
args[_i] = arguments[_i];
69+
}
70+
return localRecord.shouldReturn = args;
71+
};
72+
if (localRecord && localRecord.property === property) {
73+
if (localRecord.arguments)
74+
return thisProxy;
75+
return localRecord.shouldReturn[localRecord.currentReturnOffset++];
76+
}
77+
localRecord = createRecord();
78+
localRecord.property = property;
79+
return thisProxy;
80+
}
81+
});
82+
};
83+
return createProxy();
84+
};
85+
return Substitute;
86+
}());
87+
exports.Substitute = Substitute;

package-lock.json

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

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
"name": "@fluffy-spoon/substitute",
33
"version": "1.0.0",
44
"description": "",
5-
"main": "src/index.ts",
5+
"main": "dist/index.js",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1"
7+
"test": "ava"
88
},
99
"author": "",
10-
"license": "ISC"
10+
"license": "ISC",
11+
"devDependencies": {
12+
"typescript": "next",
13+
"@types/node": "latest"
14+
}
1115
}

src/index.ts

Lines changed: 17 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ export class Substitute {
1818
arguments: Array<any>,
1919
shouldReturn: Array<any>,
2020
currentReturnOffset: number,
21-
proxy: any
21+
proxy: any,
22+
property: string|number
2223
};
2324

2425
const createRecord = () => {
2526
lastRecord = {
2627
arguments: null,
2728
shouldReturn: [],
2829
proxy: null,
29-
currentReturnOffset: 0
30+
currentReturnOffset: 0,
31+
property: null
3032
};
3133

3234
return lastRecord;
@@ -74,80 +76,43 @@ export class Substitute {
7476
return thisProxy;
7577
},
7678
get: (target, property) => {
77-
if(typeof property === 'symbol')
79+
if(typeof property === 'symbol') {
80+
if(property === Symbol.toPrimitive)
81+
return () => void 0;
82+
7883
return void 0;
84+
}
7985

8086
if(property === 'valueOf')
8187
return void 0;
8288

8389
if(property === 'toString')
84-
return target[property].toString();
90+
return (target[property] || '').toString();
8591

8692
if(property === 'inspect')
8793
return () => "{SubstituteJS fake}";
8894

8995
if(property === 'constructor')
9096
return () => thisProxy;
9197

92-
if(property === 'returns') {
93-
return (...args: any[]) => {
94-
localRecord.shouldReturn = args;
95-
};
96-
}
98+
if(property === 'returns')
99+
return (...args: any[]) => localRecord.shouldReturn = args;
97100

98-
if(localRecord) {
101+
if(localRecord && localRecord.property === property) {
99102
if(localRecord.arguments)
100103
return thisProxy;
101104

102-
return localRecord.shouldReturn[localRecord.currentReturnOffset];
105+
return localRecord.shouldReturn[localRecord.currentReturnOffset++];
103106
}
104107

105108
localRecord = createRecord();
109+
localRecord.property = property;
110+
106111
return thisProxy;
107112
}
108113
});
109114
};
110115

111116
return createProxy() as any;
112117
}
113-
}
114-
115-
class Example {
116-
a = "1337";
117-
b = 1337;
118-
119-
c(arg1: string, arg2: string) {
120-
return "hello " + arg1 + " world (" + arg2 + ")";
121-
}
122-
123-
get d() {
124-
return 1337;
125-
}
126-
127-
set v(x) {
128-
console.log('define: ' + x);
129-
}
130-
}
131-
132-
var exFake = Substitute.for<Example>();
133-
134-
exFake.a.returns("foo", "bar");
135-
console.log('returned', exFake.a);
136-
console.log('returned', exFake.a);
137-
138-
exFake.b.returns(10, 30);
139-
exFake.c("hi", "there").returns("blah", "haha");
140-
exFake.d.returns(9);
141-
142-
console.log(exFake.a);
143-
console.log(exFake.b);
144-
145-
console.log('assert');
146-
147-
console.log(exFake.c("hi", "there"));
148-
console.log(exFake.c("hi", "the1re"));
149-
console.log(exFake.c("hi", "there"));
150-
console.log(exFake.c("hi", "there"));
151-
console.log(exFake.c("something", "there"));
152-
153-
console.log(exFake.d);
118+
}

tsconfig.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compileOnSave": true,
3+
"compilerOptions": {
4+
"outDir": "./dist",
5+
"target": "es5",
6+
"lib": [
7+
"es2015"
8+
]
9+
}
10+
}

0 commit comments

Comments
 (0)