-
Notifications
You must be signed in to change notification settings - Fork 0
/
mintest.js
296 lines (253 loc) · 8.32 KB
/
mintest.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// # mintest
//
// Tiny microlibrary for ad-hoc unit testing in a scratch file
// and for beginning practice or teaching of test-driven
// development (TDD) techniques.
//
// ![mintest in Sublime Text](mintest.gif "mintest demo")
//
// Provides strict equality comparison of boolean, numeric, and string
// values or objects containing those value types (shallow comparison).
// Logs results to the browser or Node console in terse or verbose form,
// depending on whether `test()` is passed a tests object as its second
// argument.
//
// GitHub: [https://github.com/bitfragment/mintest](https://github.com/bitfragment/mintest)
//
//
// ## Usage
//
// ```javascript
// const mintest = require('mintest.js');
// ```
//
// ### Terse results
//
// For multiple tests.
//
// ```javascript
// mintest.test(testFunction, tests);
// ```
//
// `tests` is any object with the property `tests`. The value of
// `tests` is an array of individual test objects containing input
// parameters and expected return values:
//
// ```javascript
// let myTests = {
// tests: [
// {i: -1, o: 0},
// {i: 0, o: 1}
// ]
// };
// ```
//
// ### Verbose results
//
// For single tests.
//
// `expected` is the expected value returned by the function invocation
// passed as the first argument. The third and fourth arguments,
// `message` (a custom failure message) and `name` (a custom test
// name), are optional.
//
// ```javascript
// mintest.test(testFunction([param, [param]...]]), expected, message, name);
// ```
//
//
// ## Console output
//
// Logs the test function's name if its `name` property can be
// accessed (this is not possible with anonymous functions).
//
// ```
// testFunction:✅✅✅
// testFunction:❌❌❌
// ```
//
// For more example testing code and console output, see the bottom of
// this file following the source.
//
//
//
// ## Annotated source
//
// Begin module.
const mintest = (function() {
// Mark to represent test success.
const successMark = () => String.fromCharCode(9989); // ✅
// Mark to represent test failure.
const failMark = () => String.fromCharCode(10060); // ❌
// Default test failure message.
const failMessage = () => 'Actual and expected values do not match.';
// Verbose message for test success.
const verboseSuccess = (successMark, name) =>
`${successMark} ${name} succeeded.`;
// Verbose message for test failure.
const verboseFailure = (failMark, actual, expected, message, name) =>
`${failMark} ${name} FAILED: ${message}\n` +
` [Actual: ${actual} Expected: ${expected}]`;
// Strict equality comparison.
const isEqual = (a, b) =>
(typeof a === 'object' &&
typeof b === 'object')
? isEqualObj(a, b)
: a === b;
// Shallow comparison of objects whose properties are boolean,
// numeric or string values.
const isEqualObj = (a, b) => {
for (let prop in a) {
if (b[prop] === undefined) return false;
if (a[prop] !== b[prop]) return false;
}
return true;
};
// Return a terse evaluation. Expects left and right values
// to be compared, plus success and failure marks.
const terseEval = (left, right, successMark, failMark) =>
isEqual(left, right) ? successMark : failMark;
// Return a verbose evaluation. Receives its parameters from
// `verboseTest()` and returns a string verbosely summarizing test
// results.
const verboseEval = (successMark, failMark, actual, expected, message, name) =>
isEqual(actual, expected)
? verboseSuccess(successMark, name)
: verboseFailure(failMark, actual, expected, message, name);
// Return the result of a single terse test.
const terseTest = (test, testFunction, evalFunction, successMark, failMark) => {
let keys = Object.keys(test),
input = test[keys[0]], output = test[keys[1]],
left = testFunction(input), right = output,
result = evalFunction(left, right, successMark, failMark);
return result;
}
// Log all terse test results to the console. Expects a string
// representing the name of the tested function, along with
// a tests object.
const terseTests = (testFunction, tests) => {
// TODO: the property `name` of an anonymous function cannot be
// accessed, but named arrow functions are being considered
// for a future edition of ECMAScript?
let fName = testFunction.name || 'Anonymous function';
let results = fName + ': ';
tests.tests.forEach(test => results += terseTest(
test, testFunction, terseEval, successMark(), failMark())
);
return console.log(results);
};
// Log verbose test results to the console. Expects two required
// parameters: `actual`, a value representing the actually
// evaluated return of the tested function, and `expected`, a value
// representing the expected return value of the tested
// function. Takes two additional optional parameters:
// `message`, a custom failure message, and `name`, a custom test
// name.
const verboseTest = (actual, expected, message, name) => {
name = name || 'Unnamed test';
message = message || failMessage();
let result = verboseEval(successMark(), failMark(),
actual, expected, message, name);
return console.log(result);
};
// Return an object containing the following methods.
return {
// Main test method. If second argument is an object that has
// the property `tests`, call `terseTests`; else, call
// `verboseTest`.
test: function() {
let argsArr = [].slice.apply(arguments);
(typeof argsArr[1] === 'object' &&
argsArr[1].tests !== undefined)
? terseTests.apply(this, argsArr)
: verboseTest.apply(this, argsArr);
}
};
// End module.
}());
// If we're using Node, export this module; else, make `mintest` a
// property of the browser `window` object.
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
module.exports = mintest;
} else {
window.mintest = mintest;
}
// ---
// ## Examples
//
// Uncomment the lines below, then execute this file.
/*
// ### Functions to test
function add1(n) {
return n + 1;
}
function addFoo(x) {
return x.concat('foo');
}
function getObj(x) {
return {p: x};
}
// ### Tests objects
let add1Tests = {
tests: [
{i: -1, o: 0},
{i: 0, o: 1}
]
};
let addFooTests = {
tests: [
{i: 'foo', o: 'foofoo'},
{i: ['bar'], o: ['bar', 'foo']}
]
};
let getObjTests = {
tests: [
{i: 3, o: {p: 3}},
{i: 'foo', o: {p: 'foo'}}
]
};
// ### Terse results: success
mintest.test(add1, add1Tests);
mintest.test(addFoo, addFooTests);
mintest.test(getObj, getObjTests);
// ```
// add1: ✅✅
// addFoo: ✅✅
// getObj: ✅✅
// ```
// ### Verbose results: success
// No test message or name:
mintest.test(add1(0), 1);
mintest.test(addFoo('foo'), 'foofoo');
mintest.test(addFoo(['bar']), ['bar', 'foo']);
mintest.test(getObj('foo'), {p: 'foo'});
//```
// ✅ Unnamed test succeeded.
// ✅ Unnamed test succeeded.
// ✅ Unnamed test succeeded.
// ✅ Unnamed test succeeded.
//```
// Test message, but no name:
mintest.test(addFoo('bar'), 'barfoo', 'Did not handle \"bar\" correctly.');
mintest.test(addFoo(['bar']), ['bar', 'foo'], 'Did not handle [\"bar\"] correctly.');
//```
// ✅ Unnamed test succeeded.
// ✅ Unnamed test succeeded.
//```
// Test message and name:
mintest.test(addFoo('bar'), 'barfoo', 'Did not handle \"bar\" correctly.', 'addFoo \"bar\" test');
mintest.test(addFoo(['bar']), ['bar', 'foo'], 'Did not handle [\"bar\"] correctly.', 'addFoo [\"bar\"] test');
// ```
// ✅ addFoo "bar" test succeeded.
// ✅ addFoo ["bar"] test succeeded.
// ```
// ### Verbose results: failure
mintest.test(addFoo('bar'), 'WRONG', 'Did not handle \"bar\" correctly.', 'addFoo \"bar\" test');
mintest.test(addFoo(['bar']), 'WRONG', 'Did not handle [\"bar\"] correctly.', 'addFoo [\"bar\"] test');
// ```
// ❌ addFoo "bar" test FAILED: Did not handle "bar" correctly.
// [Actual: barfoo Expected: WRONG]
// ❌ addFoo ["bar"] test FAILED: Did not handle ["bar"] correctly.
// [Actual: bar,foo Expected: WRONG]
// ```
*/