/
throws.js
353 lines (317 loc) · 10.8 KB
/
throws.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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
'use strict';
var tape = require('../');
var tap = require('tap');
var concat = require('concat-stream');
var inspect = require('object-inspect');
var assign = require('object.assign');
var stripFullStack = require('./common').stripFullStack;
var getter = function () { return 'message'; };
var messageGetterError = Object.defineProperty(
{ custom: 'error' },
'message',
{
configurable: true,
enumerable: true,
get: getter
}
);
var thrower = function () { throw messageGetterError; };
tap.test('failures', function (tt) {
tt.plan(1);
var test = tape.createHarness();
var count = 0;
test.createStream().pipe(concat(function (body) {
tt.same(stripFullStack(body.toString('utf8')), [
'TAP version 13',
'# non functions',
'ok ' + ++count + ' should throw',
'ok ' + ++count + ' should throw',
'ok ' + ++count + ' should throw',
'ok ' + ++count + ' should throw',
'ok ' + ++count + ' should throw',
'ok ' + ++count + ' should throw',
'ok ' + ++count + ' should throw',
'ok ' + ++count + ' should throw',
'# function',
'not ok ' + ++count + ' should throw',
' ---',
' operator: throws',
' expected: undefined',
' actual: undefined',
' at: Test.<anonymous> ($TEST/throws.js:$LINE:$COL)',
' stack: |-',
' Error: should throw',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/throws.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'# custom error messages',
'ok ' + ++count + ' "message" is enumerable',
'ok ' + ++count + ' { custom: \'error\', message: \'message\' }',
'ok ' + ++count + ' getter is still the same',
'# throws null',
'ok ' + ++count + ' throws null',
'# wrong type of error',
'not ok ' + ++count + ' throws actual',
' ---',
' operator: throws',
' expected: |-',
' [Function: TypeError]',
' actual: |-',
" { [RangeError: actual!] message: 'actual!' }",
' at: Test.<anonymous> ($TEST/throws.js:$LINE:$COL)',
' stack: |-',
' RangeError: actual!',
' at Test.<anonymous> ($TEST/throws.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'# object',
'ok ' + ++count + ' object properties are validated',
'# object with regexes',
'ok ' + ++count + ' object with regex values is validated',
'# similar error object',
'ok ' + ++count + ' throwing a similar error',
'# validate with regex',
'ok ' + ++count + ' regex against toString of error',
'# custom error validation',
'ok ' + ++count + ' error is SyntaxError',
'ok ' + ++count + ' error matches /value/',
'ok ' + ++count + ' unexpected error',
'# throwing primitives',
'ok ' + ++count + ' primitive: null, no expected',
'ok ' + ++count + ' primitive: undefined, no expected',
'ok ' + ++count + ' primitive: 0, no expected',
'ok ' + ++count + ' primitive: NaN, no expected',
'ok ' + ++count + ' primitive: 42, no expected',
'ok ' + ++count + ' primitive: Infinity, no expected',
'ok ' + ++count + ' primitive: \'\', no expected',
'ok ' + ++count + ' primitive: \'foo\', no expected',
'ok ' + ++count + ' primitive: true, no expected',
'ok ' + ++count + ' primitive: false, no expected',
'# ambiguous arguments',
'ok ' + ++count + ' Second',
'ok ' + ++count + ' Second',
'ok ' + ++count + ' Second',
'ok ' + ++count + ' should throw',
'not ok ' + ++count + ' should throw',
' ---',
' operator: throws',
' expected: |-',
' \'/Second$/\'',
' actual: |-',
' { [Error: First] message: \'First\' }',
' at: Test.<anonymous> ($TEST/throws.js:$LINE:$COL)',
' stack: |-',
' Error: First',
' at throwingFirst ($TEST/throws.js:$LINE:$COL)',
' [... stack stripped ...]',
' at Test.<anonymous> ($TEST/throws.js:$LINE:$COL)',
' [... stack stripped ...]',
' ...',
'# non-extensible throw match',
'ok ' + ++count + ' error is non-extensible',
'ok ' + ++count + ' non-extensible error matches',
'ok ' + ++count + ' errorWithMessage is non-extensible',
'not ok ' + ++count + ' non-extensible error with message matches',
' ---',
' operator: throws',
' expected: |-',
' { foo: 1 }',
' actual: |-',
' { message: \'abc\' }',
' at: Test.<anonymous> ($TEST/throws.js:$LINE:$COL)',
' ...',
'# frozen `message` property',
'ok ' + ++count + ' error is non-writable',
'ok ' + ++count + ' error is non-configurable',
'ok ' + ++count + ' non-writable error matches',
'',
'1..' + count,
'# tests ' + count,
'# pass 39',
'# fail ' + (count - 39),
''
]);
}));
test('non functions', function (t) {
t.plan(8);
t.throws();
t.throws(null);
t.throws(true);
t.throws(false);
t.throws('abc');
t.throws(/a/g);
t.throws([]);
t.throws({});
});
test('function', function (t) {
t.plan(1);
t.throws(function () {});
});
test('custom error messages', function (t) {
t.plan(3);
t.equal(Object.prototype.propertyIsEnumerable.call(messageGetterError, 'message'), true, '"message" is enumerable');
t.throws(thrower, "{ custom: 'error', message: 'message' }");
t.equal(Object.getOwnPropertyDescriptor(messageGetterError, 'message').get, getter, 'getter is still the same');
});
test('throws null', function (t) {
t.plan(1);
t.throws(function () { throw null; }, 'throws null');
t.end();
});
test('wrong type of error', function (t) {
t.plan(1);
var actual = new RangeError('actual!');
t.throws(function () { throw actual; }, TypeError, 'throws actual');
t.end();
});
// taken from https://nodejs.org/api/assert.html#assert_assert_throws_fn_error_message
var err = new TypeError('Wrong value');
err.code = 404;
err.foo = 'bar';
err.info = {
nested: true,
baz: 'text'
};
err.reg = /abc/i;
test('object', function (t) {
t.plan(1);
t.throws(
function () { throw err; },
{
name: 'TypeError',
message: 'Wrong value',
info: {
nested: true,
baz: 'text'
}
// Only properties on the validation object will be tested for.
// Using nested objects requires all properties to be present. Otherwise
// the validation is going to fail.
},
'object properties are validated'
);
t.end();
});
test('object with regexes', function (t) {
t.plan(1);
t.throws(
function () { throw err; },
{
// The `name` and `message` properties are strings and using regular
// expressions on those will match against the string. If they fail, an
// error is thrown.
name: /^TypeError$/,
message: /Wrong/,
foo: 'bar',
info: {
nested: true,
// It is not possible to use regular expressions for nested properties!
baz: 'text'
},
// The `reg` property contains a regular expression and only if the
// validation object contains an identical regular expression, it is going
// to pass.
reg: /abc/i
},
'object with regex values is validated'
);
t.end();
});
test('similar error object', function (t) {
t.plan(1);
t.throws(
function () {
var otherErr = new TypeError('Not found');
// Copy all enumerable properties from `err` to `otherErr`.
assign(otherErr, err);
throw otherErr;
},
// The error's `message` and `name` properties will also be checked when using
// an error as validation object.
err,
'throwing a similar error'
);
t.end();
});
test('validate with regex', function (t) {
t.plan(1);
t.throws(
function () { throw new Error('Wrong value'); },
/^Error: Wrong value$/,
'regex against toString of error'
);
t.end();
});
test('custom error validation', function (t) {
t.plan(3);
t.throws(
function () { throw new SyntaxError('Wrong value'); },
function (error) {
t.ok(error instanceof SyntaxError, 'error is SyntaxError');
t.ok((/value/).test(error), 'error matches /value/');
// Avoid returning anything from validation functions besides `true`.
// Otherwise, it's not clear what part of the validation failed. Instead,
// throw an error about the specific validation that failed (as done in this
// example) and add as much helpful debugging information to that error as
// possible.
return true;
},
'unexpected error'
);
t.end();
});
test('throwing primitives', function (t) {
[null, undefined, 0, NaN, 42, Infinity, '', 'foo', true, false].forEach(function (primitive) {
t.throws(function () { throw primitive; }, 'primitive: ' + inspect(primitive) + ', no expected');
});
t.end();
});
test('ambiguous arguments', function (t) {
function throwingFirst() {
throw new Error('First');
}
function throwingSecond() {
throw new Error('Second');
}
function notThrowing() {}
// The second argument is a string and the input function threw an Error.
// The first case will not throw as it does not match for the error message
// thrown by the input function!
t.throws(throwingFirst, 'Second');
// In the next example the message has no benefit over the message from the
// error and since it is not clear if the user intended to actually match
// against the error message, Node.js throws an `ERR_AMBIGUOUS_ARGUMENT` error.
t.throws(throwingSecond, 'Second');
// TypeError [ERR_AMBIGUOUS_ARGUMENT]
// The string is only used (as message) in case the function does not throw:
t.doesNotThrow(notThrowing, 'Second');
// AssertionError [ERR_ASSERTION]: Missing expected exception: Second
// If it was intended to match for the error message do this instead:
// It does not fail because the error messages match.
t.throws(throwingSecond, /Second$/);
// If the error message does not match, an AssertionError is thrown.
t.throws(throwingFirst, /Second$/);
// AssertionError [ERR_ASSERTION]
t.end();
});
test('non-extensible throw match', { skip: !Object.seal }, function (t) {
var error = { foo: 1 };
Object.seal(error);
t.throws(function () { error.x = 1; }, TypeError, 'error is non-extensible');
t.throws(function () { throw error; }, error, 'non-extensible error matches');
var errorWithMessage = { message: 'abc' };
Object.seal(errorWithMessage);
t.throws(function () { errorWithMessage.x = 1; }, TypeError, 'errorWithMessage is non-extensible');
t.throws(function () { throw errorWithMessage; }, error, 'non-extensible error with message matches');
t.end();
});
test('frozen `message` property', { skip: !Object.defineProperty }, function (t) {
var error = { message: 'abc' };
Object.defineProperty(error, 'message', { configurable: false, enumerable: false, writable: false });
t.throws(function () { error.message = 'def'; }, TypeError, 'error is non-writable');
t.throws(function () { delete error.message; }, TypeError, 'error is non-configurable');
t.throws(function () { throw error; }, { message: 'abc' }, 'non-writable error matches');
t.end();
});
});