Skip to content

Commit d94ffbc

Browse files
committedJan 4, 2022
refactor: Convert test to a class
1 parent f1b92fb commit d94ffbc

File tree

1 file changed

+279
-289
lines changed

1 file changed

+279
-289
lines changed
 

‎lib/test.js

+279-289
Original file line numberDiff line numberDiff line change
@@ -11,336 +11,320 @@ var https = require('https');
1111
var assert = require('assert');
1212
var Request = request.Request;
1313

14-
/**
15-
* Expose `Test`.
16-
*/
17-
18-
module.exports = Test;
19-
20-
/**
21-
* Initialize a new `Test` with the given `app`,
22-
* request `method` and `path`.
23-
*
24-
* @param {Server} app
25-
* @param {String} method
26-
* @param {String} path
27-
* @api public
28-
*/
29-
30-
function Test(app, method, path) {
31-
Request.call(this, method.toUpperCase(), path);
32-
this.redirects(0);
33-
this.buffer();
34-
this.app = app;
35-
this._asserts = [];
36-
this.url = typeof app === 'string'
37-
? app + path
38-
: this.serverAddress(app, path);
39-
}
40-
41-
/**
42-
* Inherits from `Request.prototype`.
43-
*/
44-
45-
Object.setPrototypeOf(Test.prototype, Request.prototype);
46-
47-
/**
48-
* Returns a URL, extracted from a server.
49-
*
50-
* @param {Server} app
51-
* @param {String} path
52-
* @returns {String} URL address
53-
* @api private
54-
*/
55-
56-
Test.prototype.serverAddress = function(app, path) {
57-
var addr = app.address();
58-
var port;
59-
var protocol;
60-
61-
if (!addr) this._server = app.listen(0);
62-
port = app.address().port;
63-
protocol = app instanceof https.Server ? 'https' : 'http';
64-
return protocol + '://127.0.0.1:' + port + path;
65-
};
66-
67-
/**
68-
* Wraps an assert function into another.
69-
* The wrapper function edit the stack trace of any assertion error, prepending a more useful stack to it.
70-
*
71-
* @param {Function} assertFn
72-
* @returns {Function} wrapped assert function
73-
*/
14+
class Test extends Request {
15+
/**
16+
* Initialize a new `Test` with the given `app`,
17+
* request `method` and `path`.
18+
*
19+
* @param {Server} app
20+
* @param {String} method
21+
* @param {String} path
22+
* @api public
23+
*/
24+
constructor (app, method, path) {
25+
super(method.toUpperCase(), path);
26+
27+
this.redirects(0);
28+
this.buffer();
29+
this.app = app;
30+
this._asserts = [];
31+
this.url = typeof app === 'string'
32+
? app + path
33+
: this.serverAddress(app, path);
34+
}
7435

75-
function wrapAssertFn(assertFn) {
76-
var savedStack = new Error().stack.split('\n').slice(3);
36+
/**
37+
* Returns a URL, extracted from a server.
38+
*
39+
* @param {Server} app
40+
* @param {String} path
41+
* @returns {String} URL address
42+
* @api private
43+
*/
44+
serverAddress(app, path) {
45+
var addr = app.address();
46+
var port;
47+
var protocol;
48+
49+
if (!addr) this._server = app.listen(0);
50+
port = app.address().port;
51+
protocol = app instanceof https.Server ? 'https' : 'http';
52+
return protocol + '://127.0.0.1:' + port + path;
53+
}
7754

78-
return function(res) {
79-
var badStack;
80-
var err = assertFn(res);
81-
if (err instanceof Error && err.stack) {
82-
badStack = err.stack.replace(err.message, '').split('\n').slice(1);
83-
err.stack = [err.toString()]
84-
.concat(savedStack)
85-
.concat('----')
86-
.concat(badStack)
87-
.join('\n');
55+
/**
56+
* Expectations:
57+
*
58+
* .expect(200)
59+
* .expect(200, fn)
60+
* .expect(200, body)
61+
* .expect('Some body')
62+
* .expect('Some body', fn)
63+
* .expect(['json array body', { key: 'val' }])
64+
* .expect('Content-Type', 'application/json')
65+
* .expect('Content-Type', 'application/json', fn)
66+
* .expect(fn)
67+
* .expect([200, 404])
68+
*
69+
* @return {Test}
70+
* @api public
71+
*/
72+
expect(a, b, c) {
73+
// callback
74+
if (typeof a === 'function') {
75+
this._asserts.push(wrapAssertFn(a));
76+
return this;
77+
}
78+
if (typeof b === 'function') this.end(b);
79+
if (typeof c === 'function') this.end(c);
80+
81+
// status
82+
if (typeof a === 'number') {
83+
this._asserts.push(wrapAssertFn(this._assertStatus.bind(this, a)));
84+
// body
85+
if (typeof b !== 'function' && arguments.length > 1) {
86+
this._asserts.push(wrapAssertFn(this._assertBody.bind(this, b)));
87+
}
88+
return this;
8889
}
89-
return err;
90-
};
91-
}
9290

93-
/**
94-
* Expectations:
95-
*
96-
* .expect(200)
97-
* .expect(200, fn)
98-
* .expect(200, body)
99-
* .expect('Some body')
100-
* .expect('Some body', fn)
101-
* .expect(['json array body', { key: 'val' }])
102-
* .expect('Content-Type', 'application/json')
103-
* .expect('Content-Type', 'application/json', fn)
104-
* .expect(fn)
105-
* .expect([200, 404])
106-
*
107-
* @return {Test}
108-
* @api public
109-
*/
91+
// multiple statuses
92+
if (Array.isArray(a) && a.length > 0 && a.every(val => typeof val === 'number')) {
93+
this._asserts.push(wrapAssertFn(this._assertStatusArray.bind(this, a)));
94+
return this;
95+
}
11096

111-
Test.prototype.expect = function(a, b, c) {
112-
// callback
113-
if (typeof a === 'function') {
114-
this._asserts.push(wrapAssertFn(a));
115-
return this;
116-
}
117-
if (typeof b === 'function') this.end(b);
118-
if (typeof c === 'function') this.end(c);
97+
// header field
98+
if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) {
99+
this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, { name: '' + a, value: b })));
100+
return this;
101+
}
119102

120-
// status
121-
if (typeof a === 'number') {
122-
this._asserts.push(wrapAssertFn(this._assertStatus.bind(this, a)));
123103
// body
124-
if (typeof b !== 'function' && arguments.length > 1) {
125-
this._asserts.push(wrapAssertFn(this._assertBody.bind(this, b)));
126-
}
127-
return this;
128-
}
104+
this._asserts.push(wrapAssertFn(this._assertBody.bind(this, a)));
129105

130-
// multiple statuses
131-
if (Array.isArray(a) && a.length > 0 && a.every(val => typeof val === 'number')) {
132-
this._asserts.push(wrapAssertFn(this._assertStatusArray.bind(this, a)));
133106
return this;
134107
}
135108

136-
// header field
137-
if (typeof b === 'string' || typeof b === 'number' || b instanceof RegExp) {
138-
this._asserts.push(wrapAssertFn(this._assertHeader.bind(this, { name: '' + a, value: b })));
109+
/**
110+
* Defer invoking superagent's `.end()` until
111+
* the server is listening.
112+
*
113+
* @param {Function} fn
114+
* @api public
115+
*/
116+
end(fn) {
117+
var self = this;
118+
var server = this._server;
119+
var end = Request.prototype.end;
120+
121+
end.call(this, function (err, res) {
122+
if (server && server._handle) return server.close(localAssert);
123+
124+
localAssert();
125+
126+
function localAssert() {
127+
self.assert(err, res, fn);
128+
}
129+
});
130+
139131
return this;
140132
}
141133

142-
// body
143-
this._asserts.push(wrapAssertFn(this._assertBody.bind(this, a)));
144-
145-
return this;
146-
};
147-
148-
/**
149-
* Defer invoking superagent's `.end()` until
150-
* the server is listening.
151-
*
152-
* @param {Function} fn
153-
* @api public
154-
*/
155-
156-
Test.prototype.end = function(fn) {
157-
var self = this;
158-
var server = this._server;
159-
var end = Request.prototype.end;
160-
161-
end.call(this, function(err, res) {
162-
if (server && server._handle) return server.close(localAssert);
163-
164-
localAssert();
165-
166-
function localAssert() {
167-
self.assert(err, res, fn);
134+
/**
135+
* Perform assertions and invoke `fn(err, res)`.
136+
*
137+
* @param {?Error} resError
138+
* @param {Response} res
139+
* @param {Function} fn
140+
* @api private
141+
*/
142+
assert(resError, res, fn) {
143+
var errorObj;
144+
var i;
145+
146+
// check for unexpected network errors or server not running/reachable errors
147+
// when there is no response and superagent sends back a System Error
148+
// do not check further for other asserts, if any, in such case
149+
// https://nodejs.org/api/errors.html#errors_common_system_errors
150+
var sysErrors = {
151+
ECONNREFUSED: 'Connection refused',
152+
ECONNRESET: 'Connection reset by peer',
153+
EPIPE: 'Broken pipe',
154+
ETIMEDOUT: 'Operation timed out'
155+
};
156+
157+
if (!res && resError) {
158+
if (resError instanceof Error && resError.syscall === 'connect'
159+
&& Object.getOwnPropertyNames(sysErrors).indexOf(resError.code) >= 0) {
160+
errorObj = new Error(resError.code + ': ' + sysErrors[resError.code]);
161+
} else {
162+
errorObj = resError;
163+
}
168164
}
169-
});
170165

171-
return this;
172-
};
173-
174-
/**
175-
* Perform assertions and invoke `fn(err, res)`.
176-
*
177-
* @param {?Error} resError
178-
* @param {Response} res
179-
* @param {Function} fn
180-
* @api private
181-
*/
182-
183-
Test.prototype.assert = function(resError, res, fn) {
184-
var errorObj;
185-
var i;
186-
187-
// check for unexpected network errors or server not running/reachable errors
188-
// when there is no response and superagent sends back a System Error
189-
// do not check further for other asserts, if any, in such case
190-
// https://nodejs.org/api/errors.html#errors_common_system_errors
191-
var sysErrors = {
192-
ECONNREFUSED: 'Connection refused',
193-
ECONNRESET: 'Connection reset by peer',
194-
EPIPE: 'Broken pipe',
195-
ETIMEDOUT: 'Operation timed out'
196-
};
166+
// asserts
167+
for (i = 0; i < this._asserts.length && !errorObj; i += 1) {
168+
errorObj = this._assertFunction(this._asserts[i], res);
169+
}
197170

198-
if (!res && resError) {
199-
if (resError instanceof Error && resError.syscall === 'connect'
200-
&& Object.getOwnPropertyNames(sysErrors).indexOf(resError.code) >= 0) {
201-
errorObj = new Error(resError.code + ': ' + sysErrors[resError.code]);
202-
} else {
171+
// set unexpected superagent error if no other error has occurred.
172+
if (!errorObj && resError instanceof Error && (!res || resError.status !== res.status)) {
203173
errorObj = resError;
204174
}
205-
}
206175

207-
// asserts
208-
for (i = 0; i < this._asserts.length && !errorObj; i += 1) {
209-
errorObj = this._assertFunction(this._asserts[i], res);
176+
fn.call(this, errorObj || null, res);
210177
}
211178

212-
// set unexpected superagent error if no other error has occurred.
213-
if (!errorObj && resError instanceof Error && (!res || resError.status !== res.status)) {
214-
errorObj = resError;
179+
/**
180+
* Perform assertions on a response body and return an Error upon failure.
181+
*
182+
* @param {Mixed} body
183+
* @param {Response} res
184+
* @return {?Error}
185+
* @api private
186+
*/// eslint-disable-next-line class-methods-use-this
187+
_assertBody(body, res) {
188+
var isregexp = body instanceof RegExp;
189+
var a;
190+
var b;
191+
192+
// parsed
193+
if (typeof body === 'object' && !isregexp) {
194+
try {
195+
assert.deepStrictEqual(body, res.body);
196+
} catch (err) {
197+
a = util.inspect(body);
198+
b = util.inspect(res.body);
199+
return error('expected ' + a + ' response body, got ' + b, body, res.body);
200+
}
201+
} else if (body !== res.text) {
202+
// string
203+
a = util.inspect(body);
204+
b = util.inspect(res.text);
205+
206+
// regexp
207+
if (isregexp) {
208+
if (!body.test(res.text)) {
209+
return error('expected body ' + b + ' to match ' + body, body, res.body);
210+
}
211+
} else {
212+
return error('expected ' + a + ' response body, got ' + b, body, res.body);
213+
}
214+
}
215215
}
216216

217-
fn.call(this, errorObj || null, res);
218-
};
219-
220-
/**
221-
* Perform assertions on a response body and return an Error upon failure.
222-
*
223-
* @param {Mixed} body
224-
* @param {Response} res
225-
* @return {?Error}
226-
* @api private
227-
*/
228-
229-
Test.prototype._assertBody = function(body, res) {
230-
var isregexp = body instanceof RegExp;
231-
var a;
232-
var b;
233-
234-
// parsed
235-
if (typeof body === 'object' && !isregexp) {
236-
try {
237-
assert.deepStrictEqual(body, res.body);
238-
} catch (err) {
239-
a = util.inspect(body);
240-
b = util.inspect(res.body);
241-
return error('expected ' + a + ' response body, got ' + b, body, res.body);
217+
/**
218+
* Perform assertions on a response header and return an Error upon failure.
219+
*
220+
* @param {Object} header
221+
* @param {Response} res
222+
* @return {?Error}
223+
* @api private
224+
*/// eslint-disable-next-line class-methods-use-this
225+
_assertHeader(header, res) {
226+
var field = header.name;
227+
var actual = res.header[field.toLowerCase()];
228+
var fieldExpected = header.value;
229+
230+
if (typeof actual === 'undefined') return new Error('expected "' + field + '" header field');
231+
// This check handles header values that may be a String or single element Array
232+
if ((Array.isArray(actual) && actual.toString() === fieldExpected)
233+
|| fieldExpected === actual) {
234+
return;
242235
}
243-
} else if (body !== res.text) {
244-
// string
245-
a = util.inspect(body);
246-
b = util.inspect(res.text);
247-
248-
// regexp
249-
if (isregexp) {
250-
if (!body.test(res.text)) {
251-
return error('expected body ' + b + ' to match ' + body, body, res.body);
236+
if (fieldExpected instanceof RegExp) {
237+
if (!fieldExpected.test(actual)) {
238+
return new Error('expected "' + field + '" matching '
239+
+ fieldExpected + ', got "' + actual + '"');
252240
}
253241
} else {
254-
return error('expected ' + a + ' response body, got ' + b, body, res.body);
242+
return new Error('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"');
255243
}
256244
}
257-
};
258-
259-
/**
260-
* Perform assertions on a response header and return an Error upon failure.
261-
*
262-
* @param {Object} header
263-
* @param {Response} res
264-
* @return {?Error}
265-
* @api private
266-
*/
267-
268-
Test.prototype._assertHeader = function(header, res) {
269-
var field = header.name;
270-
var actual = res.header[field.toLowerCase()];
271-
var fieldExpected = header.value;
272245

273-
if (typeof actual === 'undefined') return new Error('expected "' + field + '" header field');
274-
// This check handles header values that may be a String or single element Array
275-
if ((Array.isArray(actual) && actual.toString() === fieldExpected)
276-
|| fieldExpected === actual) {
277-
return;
278-
}
279-
if (fieldExpected instanceof RegExp) {
280-
if (!fieldExpected.test(actual)) {
281-
return new Error('expected "' + field + '" matching '
282-
+ fieldExpected + ', got "' + actual + '"');
246+
/**
247+
* Perform assertions on the response status and return an Error upon failure.
248+
*
249+
* @param {Number} status
250+
* @param {Response} res
251+
* @return {?Error}
252+
* @api private
253+
*/// eslint-disable-next-line class-methods-use-this
254+
_assertStatus(status, res) {
255+
var a;
256+
var b;
257+
if (res.status !== status) {
258+
a = http.STATUS_CODES[status];
259+
b = http.STATUS_CODES[res.status];
260+
return new Error('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"');
283261
}
284-
} else {
285-
return new Error('expected "' + field + '" of "' + fieldExpected + '", got "' + actual + '"');
286262
}
287-
};
288263

289-
/**
290-
* Perform assertions on the response status and return an Error upon failure.
291-
*
292-
* @param {Number} status
293-
* @param {Response} res
294-
* @return {?Error}
295-
* @api private
296-
*/
264+
/**
265+
* Perform assertions on the response status and return an Error upon failure.
266+
*
267+
* @param {Array<Number>} statusArray
268+
* @param {Response} res
269+
* @return {?Error}
270+
* @api private
271+
*/// eslint-disable-next-line class-methods-use-this
272+
_assertStatusArray(statusArray, res) {
273+
var b;
274+
var expectedList;
275+
if (!statusArray.includes(res.status)) {
276+
b = http.STATUS_CODES[res.status];
277+
expectedList = statusArray.join(', ');
278+
return new Error(
279+
'expected one of "' + expectedList + '", got ' + res.status + ' "' + b + '"'
280+
);
281+
}
282+
}
297283

298-
Test.prototype._assertStatus = function(status, res) {
299-
var a;
300-
var b;
301-
if (res.status !== status) {
302-
a = http.STATUS_CODES[status];
303-
b = http.STATUS_CODES[res.status];
304-
return new Error('expected ' + status + ' "' + a + '", got ' + res.status + ' "' + b + '"');
284+
/**
285+
* Performs an assertion by calling a function and return an Error upon failure.
286+
*
287+
* @param {Function} fn
288+
* @param {Response} res
289+
* @return {?Error}
290+
* @api private
291+
*/// eslint-disable-next-line class-methods-use-this
292+
_assertFunction(fn, res) {
293+
var err;
294+
try {
295+
err = fn(res);
296+
} catch (e) {
297+
err = e;
298+
}
299+
if (err instanceof Error) return err;
305300
}
306-
};
301+
}
307302

308303
/**
309-
* Perform assertions on the response status and return an Error upon failure.
304+
* Wraps an assert function into another.
305+
* The wrapper function edit the stack trace of any assertion error, prepending a more useful stack to it.
310306
*
311-
* @param {Array<Number>} statusArray
312-
* @param {Response} res
313-
* @return {?Error}
314-
* @api private
307+
* @param {Function} assertFn
308+
* @returns {Function} wrapped assert function
315309
*/
316310

317-
Test.prototype._assertStatusArray = function(statusArray, res) {
318-
var b;
319-
var expectedList;
320-
if (!statusArray.includes(res.status)) {
321-
b = http.STATUS_CODES[res.status];
322-
expectedList = statusArray.join(', ');
323-
return new Error('expected one of "' + expectedList + '", got ' + res.status + ' "' + b + '"');
324-
}
325-
};
311+
function wrapAssertFn(assertFn) {
312+
var savedStack = new Error().stack.split('\n').slice(3);
326313

327-
/**
328-
* Performs an assertion by calling a function and return an Error upon failure.
329-
*
330-
* @param {Function} fn
331-
* @param {Response} res
332-
* @return {?Error}
333-
* @api private
334-
*/
335-
Test.prototype._assertFunction = function(fn, res) {
336-
var err;
337-
try {
338-
err = fn(res);
339-
} catch (e) {
340-
err = e;
341-
}
342-
if (err instanceof Error) return err;
343-
};
314+
return function(res) {
315+
var badStack;
316+
var err = assertFn(res);
317+
if (err instanceof Error && err.stack) {
318+
badStack = err.stack.replace(err.message, '').split('\n').slice(1);
319+
err.stack = [err.toString()]
320+
.concat(savedStack)
321+
.concat('----')
322+
.concat(badStack)
323+
.join('\n');
324+
}
325+
return err;
326+
};
327+
}
344328

345329
/**
346330
* Return an `Error` with `msg` and results properties.
@@ -359,3 +343,9 @@ function error(msg, expected, actual) {
359343
err.showDiff = true;
360344
return err;
361345
}
346+
347+
/**
348+
* Expose `Test`.
349+
*/
350+
351+
module.exports = Test;

0 commit comments

Comments
 (0)
Please sign in to comment.