Note: All examples in this README are using the unversioned form of the import URL. In production you should always use the versioned import form such as `https://deno.land/x/superdeno@4.8.0/mod.ts`.
+> Note: All examples in this README are using the unversioned form of the import URL. In production you should always use the versioned import form such as `https://deno.land/x/superdeno@4.9.0/mod.ts`.
-## Example
+## Examples
You may pass a url string,
[`http.Server`](https://doc.deno.land/https/deno.land/std/http/mod.ts#Server), a
@@ -116,8 +116,8 @@ Here's an example of SuperDeno working with the Opine web framework:
```ts
import { superdeno } from "https://deno.land/x/superdeno/mod.ts";
-import { opine } from "https://deno.land/x/opine@1.9.1/mod.ts";
-export { expect } from "https://deno.land/x/expect@v0.2.9/mod.ts";
+import { opine } from "https://deno.land/x/opine@2.3.4/mod.ts";
+import { expect } from "https://deno.land/x/expect@v0.4.0/mod.ts";
const app = opine();
@@ -129,19 +129,49 @@ Deno.test("it should support regular expressions", async () => {
await superdeno(app)
.get("/")
.expect("Content-Type", /^application/)
- .end((err) => {
+ .catch((err) => {
expect(err.message).toEqual(
- 'expected "Content-Type" matching /^application/, got "text/html; charset=utf-8"',
+ 'expected "Content-Type" matching /^application/, got "text/html; charset=utf-8"'
);
});
});
```
+See more examples in the [Opine test suite](./test/superdeno.opine.test.ts).
+
+Here's an example of SuperDeno working with the Express web framework:
+
+```ts
+import { superdeno } from "https://deno.land/x/superdeno/mod.ts";
+// @deno-types="npm:@types/express@^4.17"
+import express from "npm:express@4.18.2";
+import { expect } from "https://deno.land/x/expect@v0.4.0/mod.ts";
+
+Deno.test("it should support regular expressions", async () => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("Hello Deno!");
+ });
+
+ await superdeno(app)
+ .get("/")
+ .expect("Content-Type", /^application/)
+ .catch((err) => {
+ expect(err.message).toEqual(
+ 'expected "Content-Type" matching /^application/, got "text/html; charset=utf-8"'
+ );
+ });
+});
+```
+
+See more examples in the [Express test suite](./test/superdeno.express.test.ts).
+
Here's an example of SuperDeno working with the Oak web framework:
```ts
import { superdeno } from "https://deno.land/x/superdeno/mod.ts";
-import { Application, Router } from "https://deno.land/x/oak@v10.0.0/mod.ts";
+import { Application, Router } from "https://deno.land/x/oak@v12.6.2/mod.ts";
const router = new Router();
router.get("/", (ctx) => {
@@ -171,6 +201,8 @@ Deno.test("it should support the Oak framework", () => {
});
```
+See more examples in the [Oak test suite](./test/superdeno.oak.test.ts).
+
If you are using the [Oak](https://github.com/oakserver/oak/) web framework then
it is recommended that you use the specialized
[SuperOak](https://github.com/cmorten/superoak) assertions library for
@@ -181,7 +213,7 @@ are making use of the `app.handle()` method (for example for serverless apps)
then you can write slightly less verbose tests for Oak:
```ts
-import { Application, Router } from "https://deno.land/x/oak@v10.0.0/mod.ts";
+import { Application, Router } from "https://deno.land/x/oak@v12.6.2/mod.ts";
import { superdeno } from "https://deno.land/x/superdeno/mod.ts";
const router = new Router();
@@ -194,15 +226,16 @@ const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
-Deno.test("it should support the Oak framework `app.handle` method", async () => {
- /**
- * Note that we have to bind `app` to the function otherwise `app.handle`
- * doesn't preserve the `this` context from `app`.
- */
- await superdeno(app.handle.bind(app))
- .get("/")
- .expect("Hello Deno!");
-});
+Deno.test(
+ "it should support the Oak framework `app.handle` method",
+ async () => {
+ /**
+ * Note that we have to bind `app` to the function otherwise `app.handle`
+ * doesn't preserve the `this` context from `app`.
+ */
+ await superdeno(app.handle.bind(app)).get("/").expect("Hello Deno!");
+ }
+);
```
In this case, SuperDeno handles the setup and closing of the server for you, so
diff --git a/deps.ts b/deps.ts
index 24c6bca..7b7a725 100644
--- a/deps.ts
+++ b/deps.ts
@@ -1,6 +1,10 @@
-export { Server } from "https://deno.land/std@0.129.0/http/server.ts";
-export { STATUS_TEXT } from "https://deno.land/std@0.129.0/http/http_status.ts";
-export { assertEquals } from "https://deno.land/std@0.129.0/testing/asserts.ts";
-export { methods } from "https://deno.land/x/opine@2.1.2/src/methods.ts";
-export { mergeDescriptors } from "https://deno.land/x/opine@2.1.2/src/utils/mergeDescriptors.ts";
+export { Server } from "https://deno.land/std@0.213.0/http/server.ts";
+export { STATUS_TEXT } from "https://deno.land/std@0.213.0/http/status.ts";
+export type { StatusCode } from "https://deno.land/std@0.213.0/http/status.ts";
+export { assertEquals } from "https://deno.land/std@0.213.0/assert/mod.ts";
+export { methods } from "https://deno.land/x/opine@2.3.4/src/methods.ts";
+export { mergeDescriptors } from "https://deno.land/x/opine@2.3.4/src/utils/mergeDescriptors.ts";
+export { getFreePort } from "https://deno.land/x/free_port@v1.2.0/mod.ts";
+
+// TODO: upgrade to v8
export { default as superagent } from "https://jspm.dev/superagent@6.1.0";
diff --git a/docs/classes/_test_.test.html b/docs/classes/_test_.test.html
index 9afa94a..8114c18 100644
--- a/docs/classes/_test_.test.html
+++ b/docs/classes/_test_.test.html
@@ -179,7 +179,7 @@ constructor
Parameters
@@ -213,7 +213,7 @@ Private #asserts
#asserts: any[]
@@ -223,7 +223,7 @@ Private #redirectList
#redirectList: string[]
@@ -233,7 +233,7 @@ Private #redirects
#redirects: number
@@ -243,7 +243,7 @@ Private #server
@@ -253,7 +253,7 @@ app
@@ -264,7 +264,7 @@ cookies
@@ -275,7 +275,7 @@ method
@@ -286,7 +286,7 @@ url
@@ -303,7 +303,7 @@ Private #assert
Private #assertBody
Private #assertFunction
@@ -407,7 +407,7 @@
@@ -450,7 +450,7 @@
@@ -485,7 +485,7 @@
@@ -551,7 +551,7 @@
Private #assertHeader
Private #assertStatus
Private #redirect
Parameters
@@ -511,7 +511,7 @@Private #serverAddress
abort
Returns void
@@ -569,7 +569,7 @@accept
Parameters
@@ -593,7 +593,7 @@agent
Parameters
@@ -617,7 +617,7 @@attach
Parameters
@@ -648,7 +648,7 @@auth
Parameters
@@ -674,7 +674,7 @@Returns this
@@ -793,7 +793,7 @@
@@ -887,7 +887,7 @@
@@ -910,7 +910,7 @@
Inherited from IRequest.auth
-- Defined in home/runner/work/superdeno/superdeno/src/test.ts:114
+ - Defined in git/asos-craigmorten/superdeno/src/test.ts:114
Parameters
@@ -706,7 +706,7 @@buffer
Parameters
@@ -730,7 +730,7 @@ca
Parameters
@@ -754,7 +754,7 @@catch
cert
Parameters
@@ -817,7 +817,7 @@clearTimeout
Returns this
@@ -835,7 +835,7 @@disableTLSCerts
Returns this
@@ -853,7 +853,7 @@end
expect
Returns this
@@ -937,7 +937,7 @@
Returns this
@@ -967,7 +967,7 @@
Returns this
@@ -995,7 +995,7 @@
Returns this
@@ -1036,7 +1036,7 @@
field
Parameters
@@ -1054,7 +1054,7 @@Returns this
Inherited from IRequest.field
-- Defined in home/runner/work/superdeno/superdeno/src/test.ts:122
+ - Defined in git/asos-craigmorten/superdeno/src/test.ts:122
Parameters
@@ -1083,7 +1083,7 @@get
Parameters
@@ -1107,7 +1107,7 @@http2
Parameters
@@ -1131,7 +1131,7 @@key
Parameters
@@ -1155,7 +1155,7 @@maxResponseSize
Parameters
@@ -1179,7 +1179,7 @@ok
Parameters
@@ -1224,7 +1224,7 @@on
Parameters
@@ -1260,7 +1260,7 @@Returns this
Inherited from IRequest.on
-- Defined in home/runner/work/superdeno/superdeno/src/test.ts:128
+ - Defined in git/asos-craigmorten/superdeno/src/test.ts:128
Parameters
@@ -1296,7 +1296,7 @@Returns this
Inherited from IRequest.on
-- Defined in home/runner/work/superdeno/superdeno/src/test.ts:129
+ - Defined in git/asos-craigmorten/superdeno/src/test.ts:129
Parameters
@@ -1332,7 +1332,7 @@Returns this
Inherited from IRequest.on
-- Defined in home/runner/work/superdeno/superdeno/src/test.ts:130
+ - Defined in git/asos-craigmorten/superdeno/src/test.ts:130
Parameters
@@ -1377,7 +1377,7 @@parse
Parameters
@@ -1401,7 +1401,7 @@part
Returns this
@@ -1419,7 +1419,7 @@pfx
Parameters
@@ -1443,7 +1443,7 @@pipe
Parameters
@@ -1470,7 +1470,7 @@query
Parameters
@@ -1494,7 +1494,7 @@redirects
Parameters
@@ -1518,7 +1518,7 @@responseType
Parameters
@@ -1542,7 +1542,7 @@retry
Parameters
@@ -1569,7 +1569,7 @@send
Parameters
@@ -1593,7 +1593,7 @@serialize
Parameters
@@ -1619,7 +1619,7 @@set
Parameters
@@ -1634,7 +1634,7 @@Returns this
Inherited from IRequest.set
-- Defined in home/runner/work/superdeno/superdeno/src/test.ts:147
+ - Defined in git/asos-craigmorten/superdeno/src/test.ts:147
Parameters
@@ -1652,7 +1652,7 @@Returns this
@@ -1727,7 +1727,7 @@
Error: ErrorConstructor
@@ -132,7 +132,7 @@ method: string
@@ -153,7 +153,7 @@ path: string
@@ -174,7 +174,7 @@ status: number
@@ -194,7 +194,7 @@ text: string
diff --git a/docs/interfaces/_test_.irequest.html b/docs/interfaces/_test_.irequest.html
index 8a62831..51cf887 100644
--- a/docs/interfaces/_test_.irequest.html
+++ b/docs/interfaces/_test_.irequest.html
@@ -161,7 +161,7 @@
@@ -195,7 +195,7 @@ cookies: string
@@ -205,7 +205,7 @@ method: string
@@ -215,7 +215,7 @@ url: string
@@ -232,7 +232,7 @@
Inherited from IRequest.set
-- Defined in home/runner/work/superdeno/superdeno/src/test.ts:148
+ - Defined in git/asos-craigmorten/superdeno/src/test.ts:148
Parameters
@@ -1679,7 +1679,7 @@then
timeout
Parameters
@@ -1751,7 +1751,7 @@trustLocalhost
Parameters
@@ -1775,7 +1775,7 @@type
Parameters
@@ -1799,7 +1799,7 @@unset
Parameters
@@ -1823,7 +1823,7 @@use
Parameters
@@ -1847,7 +1847,7 @@withCredentials
Returns this
@@ -1865,7 +1865,7 @@write
Parameters
diff --git a/docs/index.html b/docs/index.html index b88f56a..1e76906 100644 --- a/docs/index.html +++ b/docs/index.html @@ -82,7 +82,7 @@SuperDeno
- + @@ -143,7 +143,7 @@
Installation
SuperDeno is also available on nest.land, a package registry for Deno on the Blockchain.
Example
diff --git a/docs/interfaces/_superdeno_.superdeno.html b/docs/interfaces/_superdeno_.superdeno.html index cf3b193..ff4153e 100644 --- a/docs/interfaces/_superdeno_.superdeno.html +++ b/docs/interfaces/_superdeno_.superdeno.html @@ -133,7 +133,7 @@checkout
Parameters
@@ -156,7 +156,7 @@connect
Parameters
@@ -179,7 +179,7 @@copy
Parameters
@@ -202,7 +202,7 @@delete
Parameters
@@ -225,7 +225,7 @@get
Parameters
@@ -248,7 +248,7 @@head
Parameters
@@ -271,7 +271,7 @@lock
Parameters
@@ -294,7 +294,7 @@m-search
Parameters
@@ -317,7 +317,7 @@merge
Parameters
@@ -340,7 +340,7 @@mkactivity
Parameters
@@ -363,7 +363,7 @@mkcol
Parameters
@@ -386,7 +386,7 @@move
Parameters
@@ -409,7 +409,7 @@notify
Parameters
@@ -432,7 +432,7 @@options
Parameters
@@ -455,7 +455,7 @@patch
Parameters
@@ -478,7 +478,7 @@post
Parameters
@@ -501,7 +501,7 @@propfind
Parameters
@@ -524,7 +524,7 @@proppatch
Parameters
@@ -547,7 +547,7 @@purge
Parameters
@@ -570,7 +570,7 @@put
Parameters
@@ -593,7 +593,7 @@report
Parameters
@@ -616,7 +616,7 @@search
Parameters
@@ -639,7 +639,7 @@subscribe
Parameters
@@ -662,7 +662,7 @@trace
Parameters
@@ -685,7 +685,7 @@unlock
Parameters
@@ -708,7 +708,7 @@unsubscribe
Parameters
diff --git a/docs/interfaces/_test_.httperror.html b/docs/interfaces/_test_.httperror.html index f7dc713..431879f 100644 --- a/docs/interfaces/_test_.httperror.html +++ b/docs/interfaces/_test_.httperror.html @@ -121,7 +121,7 @@Error
message
@@ -142,7 +142,7 @@method
name
@@ -163,7 +163,7 @@path
Optional stack
@@ -184,7 +184,7 @@status
text
constructor
cookies
method
url
abort
Returns void
@@ -249,7 +249,7 @@accept
Parameters
@@ -272,7 +272,7 @@agent
Parameters
@@ -295,7 +295,7 @@attach
Parameters
@@ -325,7 +325,7 @@auth
Parameters
@@ -350,7 +350,7 @@Returns this
@@ -466,7 +466,7 @@
Parameters
@@ -381,7 +381,7 @@buffer
Parameters
@@ -404,7 +404,7 @@ca
Parameters
@@ -428,7 +428,7 @@catch
cert
Parameters
@@ -489,7 +489,7 @@clearTimeout
Returns this
@@ -506,7 +506,7 @@disableTLSCerts
Returns this
@@ -523,7 +523,7 @@end
Parameters
@@ -547,7 +547,7 @@field
Parameters
@@ -564,7 +564,7 @@Returns this
Parameters
@@ -592,7 +592,7 @@get
Parameters
@@ -615,7 +615,7 @@http2
Parameters
@@ -638,7 +638,7 @@key
Parameters
@@ -661,7 +661,7 @@maxResponseSize
Parameters
@@ -684,7 +684,7 @@ok
Parameters
@@ -728,7 +728,7 @@on
Parameters
@@ -763,7 +763,7 @@Returns this
Parameters
@@ -798,7 +798,7 @@Returns this
Parameters
@@ -833,7 +833,7 @@Returns this
Parameters
@@ -877,7 +877,7 @@parse
Parameters
@@ -900,7 +900,7 @@part
Returns this
@@ -917,7 +917,7 @@pfx
Parameters
@@ -940,7 +940,7 @@pipe
Parameters
@@ -966,7 +966,7 @@query
Parameters
@@ -989,7 +989,7 @@redirects
Parameters
@@ -1012,7 +1012,7 @@responseType
Parameters
@@ -1035,7 +1035,7 @@retry
Parameters
@@ -1061,7 +1061,7 @@send
Parameters
@@ -1084,7 +1084,7 @@serialize
Parameters
@@ -1109,7 +1109,7 @@set
Parameters
@@ -1123,7 +1123,7 @@Returns this
Parameters
@@ -1140,7 +1140,7 @@Returns this
@@ -1214,7 +1214,7 @@
accepted: boolean
@@ -138,7 +138,7 @@ badRequest: boolean
@@ -148,7 +148,7 @@ body: any
@@ -158,7 +158,7 @@ charset: string
@@ -168,7 +168,7 @@ clientError: boolean
@@ -178,7 +178,7 @@ error: false | HTTPError
@@ -188,7 +188,7 @@ files: any
@@ -198,7 +198,7 @@ forbidden: boolean
@@ -208,7 +208,7 @@ header: Header
@@ -218,7 +218,7 @@ headers: Header
@@ -228,7 +228,7 @@ info: boolean
@@ -238,7 +238,7 @@ links: object
@@ -248,7 +248,7 @@ noContent: boolean
@@ -258,7 +258,7 @@ notAcceptable: boolean
@@ -268,7 +268,7 @@ notFound: boolean
@@ -278,7 +278,7 @@ ok: boolean
@@ -288,7 +288,7 @@ redirect: boolean
@@ -298,7 +298,7 @@ redirects: string[]
@@ -308,7 +308,7 @@ serverError: boolean
@@ -318,7 +318,7 @@ status: number
@@ -328,7 +328,7 @@ statusCode: number
@@ -338,7 +338,7 @@ statusText: string
@@ -348,7 +348,7 @@ statusType: number
@@ -358,7 +358,7 @@ text: string
@@ -368,7 +368,7 @@ type: string
@@ -378,7 +378,7 @@ unauthorized: boolean
@@ -388,7 +388,7 @@ xhr: XMLHttpRequest
@@ -405,7 +405,7 @@
listener: Deno.Listener
@@ -119,7 +119,7 @@
addrs: Deno.Addr[]
@@ -120,7 +120,7 @@
Parameters
@@ -1167,7 +1167,7 @@then
timeout
Parameters
@@ -1237,7 +1237,7 @@trustLocalhost
Parameters
@@ -1260,7 +1260,7 @@type
Parameters
@@ -1283,7 +1283,7 @@unset
Parameters
@@ -1306,7 +1306,7 @@use
Parameters
@@ -1329,7 +1329,7 @@withCredentials
Returns this
@@ -1346,7 +1346,7 @@write
Parameters
diff --git a/docs/interfaces/_test_.iresponse.html b/docs/interfaces/_test_.iresponse.html index 18caec5..7101b69 100644 --- a/docs/interfaces/_test_.iresponse.html +++ b/docs/interfaces/_test_.iresponse.html @@ -128,7 +128,7 @@accepted
badRequest
body
charset
clientError
error
files
forbidden
header
headers
info
links
noContent
notAcceptable
notFound
ok
redirect
redirects
serverError
status
statusCode
statusText
statusType
text
type
unauthorized
xhr
get
Parameters
diff --git a/docs/interfaces/_types_.legacyserverlike.html b/docs/interfaces/_types_.legacyserverlike.html index b658d86..dc4e352 100644 --- a/docs/interfaces/_types_.legacyserverlike.html +++ b/docs/interfaces/_types_.legacyserverlike.html @@ -102,7 +102,7 @@listener
close
Returns void
diff --git a/docs/interfaces/_types_.listenerlike.html b/docs/interfaces/_types_.listenerlike.html index 06bea79..8d09ebb 100644 --- a/docs/interfaces/_types_.listenerlike.html +++ b/docs/interfaces/_types_.listenerlike.html @@ -100,7 +100,7 @@listen
Parameters
diff --git a/docs/interfaces/_types_.nativeserverlike.html b/docs/interfaces/_types_.nativeserverlike.html index 3d483b9..5bd5bb4 100644 --- a/docs/interfaces/_types_.nativeserverlike.html +++ b/docs/interfaces/_types_.nativeserverlike.html @@ -103,7 +103,7 @@Readonly addrs
close
Returns void
@@ -137,7 +137,7 @@listenAndServe
Returns Promise<void>
diff --git a/docs/interfaces/_types_.requesthandlerlike.html b/docs/interfaces/_types_.requesthandlerlike.html index d8939bd..d9c1d99 100644 --- a/docs/interfaces/_types_.requesthandlerlike.html +++ b/docs/interfaces/_types_.requesthandlerlike.html @@ -84,7 +84,7 @@Callable
Parameters
diff --git a/docs/modules/_close_.html b/docs/modules/_close_.html index fad4257..cc64f6f 100644 --- a/docs/modules/_close_.html +++ b/docs/modules/_close_.html @@ -89,7 +89,7 @@Private
diff --git a/docs/modules/_superagent_.html b/docs/modules/_superagent_.html
index 8903fd2..07ab632 100644
--- a/docs/modules/_superagent_.html
+++ b/docs/modules/_superagent_.html
@@ -91,7 +91,7 @@ superagent: any = _superagent
@@ -108,7 +108,7 @@
diff --git a/docs/modules/_test_.html b/docs/modules/_test_.html
index c3169f1..23bd464 100644
--- a/docs/modules/_test_.html
+++ b/docs/modules/_test_.html
@@ -125,7 +125,7 @@ CallbackHandler: (err: any, res: IResponse) => void
@@ -164,7 +164,7 @@ ExpectChecker: (res: IResponse) => any
@@ -200,7 +200,7 @@ Header: {}
@@ -218,7 +218,7 @@ HeaderValue: string | string[]
@@ -228,7 +228,7 @@ MultipartValue: MultipartValueSingle | MultipartValueSingle[]
@@ -238,7 +238,7 @@ MultipartValueSingle: Blob | Uint8Array | Deno.Reader | string | boolean | number
@@ -248,7 +248,7 @@ Parser: (str: string) => any
@@ -279,7 +279,7 @@ Plugin: (req: IRequest) => void
@@ -310,7 +310,7 @@ Serializer: (obj: any) => string
@@ -344,7 +344,7 @@ SHAM_SYMBOL: any = Symbol("SHAM_SYMBOL")
@@ -360,7 +360,7 @@ SuperRequest: IRequest = (superagent as any).Request
@@ -382,7 +382,7 @@
@@ -416,7 +416,7 @@
Const superagent
getXHR
Returns any
diff --git a/docs/modules/_superdeno_.html b/docs/modules/_superdeno_.html index 0f0bbbd..d4bd0a7 100644 --- a/docs/modules/_superdeno_.html +++ b/docs/modules/_superdeno_.html @@ -96,7 +96,7 @@startManagedServer
Parameters
@@ -122,7 +122,7 @@superdeno
CallbackHandler
ExpectChecker
Header
HeaderValue
MultipartValue
MultipartValueSingle
Parser
Plugin
Serializer
Const SHAM_SYMBOL
Const SuperRequest
Private cleanHeader
Private completeXhrPr
@@ -438,7 +438,7 @@
@@ -474,7 +474,7 @@
@@ -505,7 +505,7 @@
diff --git a/docs/modules/_types_.html b/docs/modules/_types_.html
index 953d63a..fcfd293 100644
--- a/docs/modules/_types_.html
+++ b/docs/modules/_types_.html
@@ -94,7 +94,7 @@ ServerLike: LegacyServerLike | NativeServerLike
diff --git a/docs/modules/_utils_.html b/docs/modules/_utils_.html
index ae1e7e0..c9e38ce 100644
--- a/docs/modules/_utils_.html
+++ b/docs/modules/_utils_.html
@@ -94,7 +94,7 @@
Private error
Private initHeaders
Private isRedirect
ServerLike
Const isCommonServer
Parameters
@@ -117,7 +117,7 @@Const isListener
Parameters
@@ -140,7 +140,7 @@Const isServer
Parameters
@@ -163,7 +163,7 @@Const isStdLegacyS
Parameters
@@ -186,7 +186,7 @@Const isStdNativeS
;
+ #urlSetupPromise: Promise;
public app: string | ListenerLike | ServerLike;
- public url: string;
+ public url!: string;
constructor(
app: string | ListenerLike | ServerLike,
@@ -220,8 +238,21 @@ export class Test extends SuperRequest {
this.app = app;
this.#asserts = [];
+ let serverSetupPromiseResolver!: () => void;
+ let addressSetupPromiseResolver!: () => void;
+
+ this.#serverSetupPromise = new Promise((resolve) => {
+ serverSetupPromiseResolver = resolve;
+ });
+ this.#urlSetupPromise = new Promise((resolve) => {
+ addressSetupPromiseResolver = resolve;
+ });
+
if (isString(app)) {
this.url = `${app}${path}`;
+
+ serverSetupPromiseResolver();
+ addressSetupPromiseResolver();
} else {
if (isStdNativeServer(app)) {
const listenAndServePromise = app.listenAndServe().catch((err) =>
@@ -240,18 +271,60 @@ export class Test extends SuperRequest {
addrs: app.addrs,
async listenAndServe() {},
};
+
+ serverSetupPromiseResolver();
+ } else if (isExpressServer(app)) {
+ this.#server = app as ExpressServerLike;
+
+ const expressResolver = async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1));
+ serverSetupPromiseResolver();
+ };
+
+ if (!this.#server.listening) {
+ (this.#server as ExpressServerLike).once(
+ "listening",
+ expressResolver,
+ );
+ } else {
+ expressResolver();
+ }
} else if (isServer(app)) {
this.#server = app as ServerLike;
+
+ serverSetupPromiseResolver();
+ } else if (isExpressListener(app)) {
+ secure = false;
+
+ const expressResolver = async () => {
+ await new Promise((resolve) => setTimeout(resolve, 1));
+ serverSetupPromiseResolver();
+ };
+
+ getFreePort(random(1024, 49151)).then(
+ (freePort) => {
+ this.#server = (app as ExpressListenerLike).listen(
+ freePort,
+ expressResolver,
+ );
+ },
+ );
} else if (isListener(app)) {
secure = false;
+
this.#server = (app as ListenerLike).listen(":0");
+
+ serverSetupPromiseResolver();
} else {
+ serverSetupPromiseResolver();
+ addressSetupPromiseResolver();
+
throw new Error(
"superdeno is unable to identify or create a valid test server",
);
}
- this.url = this.#serverAddress(path, host, secure);
+ this.#setServerAddress(addressSetupPromiseResolver, path, host, secure);
}
}
@@ -265,19 +338,28 @@ export class Test extends SuperRequest {
* @returns {string} URL address
* @private
*/
- #serverAddress = (
+ #setServerAddress = async (
+ addressSetupPromiseResolver: () => void,
path: string,
host?: string,
secure?: boolean,
) => {
+ await this.#serverSetupPromise;
+
const address =
("addrs" in this.#server
? this.#server.addrs[0]
+ : "address" in this.#server
+ ? this.#server.address()
: this.#server.listener.addr) as Deno.NetAddr;
+
const port = address.port;
const protocol = secure ? "https" : "http";
+ const url = `${protocol}://${(host || "127.0.0.1")}:${port}${path}`;
- return `${protocol}://${(host || "127.0.0.1")}:${port}${path}`;
+ this.url = url;
+
+ addressSetupPromiseResolver();
};
/**
@@ -455,29 +537,33 @@ export class Test extends SuperRequest {
* @public
*/
end(callback?: CallbackHandler): this {
- const self = this;
- const end = SuperRequest.prototype.end;
-
- end.call(
- self,
- function (err: any, res: any) {
- // Before we close, ensure that we have handled all
- // requested redirects
- const redirect = isRedirect(res?.statusCode);
- const max: number = (self as any)._maxRedirects;
-
- if (redirect && self.#redirects++ !== max) {
- return self.#redirect(res, callback);
- }
+ Promise.allSettled([this.#serverSetupPromise, this.#urlSetupPromise]).then(
+ () => {
+ const self = this;
+ const end = SuperRequest.prototype.end;
+
+ end.call(
+ self,
+ function (err: any, res: any) {
+ // Before we close, ensure that we have handled all
+ // requested redirects
+ const redirect = isRedirect(res?.statusCode);
+ const max: number = (self as any)._maxRedirects;
+
+ if (redirect && self.#redirects++ !== max) {
+ return self.#redirect(res, callback);
+ }
- return close(self.#server, self.app, undefined, async () => {
- await completeXhrPromises();
+ return close(self.#server, self.app, undefined, async () => {
+ await completeXhrPromises();
- // REF: https://github.com/denoland/deno/blob/987716798fb3bddc9abc7e12c25a043447be5280/ext/timers/01_timers.js#L353
- await new Promise((resolve) => setTimeout(resolve, 20));
+ // REF: https://github.com/denoland/deno/blob/987716798fb3bddc9abc7e12c25a043447be5280/ext/timers/01_timers.js#L353
+ await new Promise((resolve) => setTimeout(resolve, 20));
- self.#assert(err, res, callback);
- });
+ self.#assert(err, res, callback);
+ });
+ },
+ );
},
);
@@ -618,8 +704,8 @@ export class Test extends SuperRequest {
*/
#assertStatus = (status: number, res: IResponse): Error | void => {
if (res.status !== status) {
- const a = STATUS_TEXT.get(status);
- const b = STATUS_TEXT.get(res.status);
+ const a = STATUS_TEXT[status as StatusCode];
+ const b = STATUS_TEXT[res.status as StatusCode];
return new Error(`expected ${status} "${a}", got ${res.status} "${b}"`);
}
diff --git a/src/types.ts b/src/types.ts
index 6ad2382..e45b384 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,6 +3,7 @@
export interface RequestHandlerLike {
(
req: any,
+ ...args: any[]
): Promise | Promise | any | void;
}
@@ -17,8 +18,22 @@ export interface NativeServerLike {
close(): void;
}
-export type ServerLike = LegacyServerLike | NativeServerLike;
+export interface ExpressServerLike {
+ address(): any;
+ listening: boolean;
+ close(): void;
+ once(eventName: string, listener: () => void): void;
+}
+
+export type ServerLike =
+ | LegacyServerLike
+ | NativeServerLike
+ | ExpressServerLike;
export interface ListenerLike {
listen(addr: string): ServerLike;
}
+
+export interface ExpressListenerLike {
+ listen(port: number, callback: () => void): ServerLike;
+}
diff --git a/src/utils.ts b/src/utils.ts
index ea41146..d638d4e 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -1,4 +1,5 @@
import type {
+ ExpressListenerLike,
LegacyServerLike,
ListenerLike,
NativeServerLike,
@@ -11,6 +12,14 @@ export const isString = (thing: unknown): thing is string =>
export const isListener = (thing: unknown): thing is ListenerLike =>
thing instanceof Object && thing !== null && "listen" in thing;
+export const isExpressListener = (
+ thing: unknown,
+): thing is ExpressListenerLike =>
+ thing instanceof Object && thing !== null && "locals" in thing &&
+ "mountpath" in thing && "all" in thing && "engine" in thing &&
+ "listen" in thing && "param" in thing && "path" in thing &&
+ "render" in thing && "route" in thing && "set" in thing && "use" in thing;
+
const isCommonServer = (thing: unknown): thing is ServerLike =>
thing instanceof Object && thing !== null && "close" in thing;
@@ -22,5 +31,11 @@ export const isStdNativeServer = (thing: unknown): thing is NativeServerLike =>
isCommonServer(thing) &&
"addrs" in thing;
+export const isExpressServer = (thing: unknown): thing is NativeServerLike =>
+ isCommonServer(thing) &&
+ "listening" in thing &&
+ "address" in thing && typeof thing.address === "function";
+
export const isServer = (thing: unknown): thing is ServerLike =>
- isStdLegacyServer(thing) || isStdNativeServer(thing);
+ isStdLegacyServer(thing) || isStdNativeServer(thing) ||
+ isExpressServer(thing);
diff --git a/src/xhrSham.js b/src/xhrSham.js
index 0cfa310..a9c8820 100644
--- a/src/xhrSham.js
+++ b/src/xhrSham.js
@@ -4,9 +4,9 @@ const decoder = new TextDecoder("utf-8");
let SHAM_SYMBOL = Symbol("SHAM_SYMBOL");
function setupSham(symbol) {
- window[symbol] = window[symbol] || {};
- window[symbol].idSequence = 0;
- window[symbol].promises = {};
+ globalThis[symbol] = globalThis[symbol] || {};
+ globalThis[symbol].idSequence = 0;
+ globalThis[symbol].promises = {};
}
setupSham(SHAM_SYMBOL);
@@ -25,7 +25,7 @@ export const exposeSham = (symbol) => {
*/
export class XMLHttpRequestSham {
constructor() {
- this.id = (++window[SHAM_SYMBOL].idSequence).toString(36);
+ this.id = (++globalThis[SHAM_SYMBOL].idSequence).toString(36);
this.origin = null;
this.onreadystatechange = () => {};
this.readyState = 0;
@@ -205,7 +205,7 @@ export class XMLHttpRequestSham {
// in this implementation. TBC whether this is accurate.
// To prevent a memory leak we clean up our promise from the
// cache now that it _must_ be resolved.
- delete window[SHAM_SYMBOL].promises[self.id];
+ delete globalThis[SHAM_SYMBOL].promises[self.id];
xhrResponse.responseHeaders = xhrResponse.getAllResponseHeaders();
onStateChange(xhrResponse);
@@ -278,7 +278,7 @@ export class XMLHttpRequestSham {
// so that superdeno can await these promises before ending.
// Not doing such results in Deno test complaining of unhandled
// async operations.
- window[SHAM_SYMBOL].promises[self.id] = fetch(options.url, {
+ globalThis[SHAM_SYMBOL].promises[self.id] = fetch(options.url, {
method: options.method,
headers: options.requestHeaders,
body,
@@ -295,7 +295,7 @@ export class XMLHttpRequestSham {
});
// Wait on the response, and then read the buffer.
- response = await window[SHAM_SYMBOL].promises[self.id];
+ response = await globalThis[SHAM_SYMBOL].promises[self.id];
// Manually transfer over properties, getPropertyDescriptors / prototype access now
// restricted in Deno. REF: https://github.com/denoland/deno/releases/tag/v1.9.0
diff --git a/test/deps.ts b/test/deps.ts
index b76caa5..1edc171 100644
--- a/test/deps.ts
+++ b/test/deps.ts
@@ -1,5 +1,9 @@
-export { dirname, join } from "https://deno.land/std@0.129.0/path/mod.ts";
-export { expect } from "https://deno.land/x/expect@v0.2.9/mod.ts";
-export * as Opine from "https://deno.land/x/opine@2.1.2/mod.ts";
-export * as Oak from "https://deno.land/x/oak@v10.4.0/mod.ts";
-export { getFreePort } from "https://deno.land/x/free_port@v1.2.0/mod.ts";
+export { dirname, join } from "https://deno.land/std@0.213.0/path/mod.ts";
+export { expect } from "https://deno.land/x/expect@v0.4.0/mod.ts";
+export * as Opine from "https://deno.land/x/opine@2.3.4/mod.ts";
+
+// TODO: upgrade to v13.0.0 - appear to be getting error when using AbortController
+export * as Oak from "https://deno.land/x/oak@v12.6.2/mod.ts";
+
+// @deno-types="npm:@types/express@^4.17"
+export { default as express } from "npm:express@4.18.2";
diff --git a/test/superdeno.express.test.ts b/test/superdeno.express.test.ts
new file mode 100644
index 0000000..343e6eb
--- /dev/null
+++ b/test/superdeno.express.test.ts
@@ -0,0 +1,964 @@
+import { getFreePort } from "../deps.ts";
+import { expect, express } from "./deps.ts";
+import { describe, it, random } from "./utils.ts";
+import { superdeno, Test } from "../mod.ts";
+
+const { json } = express;
+
+describe("superdeno(url)", () => {
+ it("superdeno(url): should support `superdeno(url)`", async (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hello");
+ });
+
+ const freePort = await getFreePort(random(1024, 49151));
+ const server = app.listen(freePort);
+ const address = server.address();
+ const url = `http://localhost:${address.port}`;
+
+ superdeno(url)
+ .get("/")
+ .expect("hello", () => {
+ server.close();
+ done();
+ });
+ });
+
+ describe(".end(cb)", () => {
+ it("superdeno(url): .end(cb): should set `this` to the test object when calling the `cb` in `.end(cb)`", async (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hello");
+ });
+
+ const freePort = await getFreePort(random(1024, 49151));
+ const server = app.listen(freePort);
+ const address = server.address();
+ const url = `http://localhost:${address.port}`;
+
+ const test = superdeno(url).get("/");
+
+ test.end(function (this: Test, _err, _res) {
+ expect(test).toEqual(this);
+ server.close();
+ done();
+ });
+ });
+ });
+});
+
+describe("superdeno(app)", () => {
+ it("superdeno(app): should fire up the app on an ephemeral port", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .end((_err, res) => {
+ expect(res.status).toEqual(200);
+ expect(res.text).toEqual("hey");
+ done();
+ });
+ });
+
+ it("superdeno(app): should work with an active server", async (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ const freePort = await getFreePort(random(1024, 49151));
+ const server = app.listen(freePort);
+
+ superdeno(server)
+ .get("/")
+ .end((_err, res) => {
+ expect(res.status).toEqual(200);
+ expect(res.text).toEqual("hey");
+ done();
+ });
+ });
+
+ it("superdeno(app): should work with remote server", async (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ const freePort = await getFreePort(random(1024, 49151));
+ const server = app.listen(freePort);
+
+ superdeno(`http://localhost:${freePort}`)
+ .get("/")
+ .end((err, res) => {
+ if (err) throw err;
+ expect(res.status).toEqual(200);
+ expect(res.text).toEqual("hey");
+ server.close();
+ done();
+ });
+ });
+
+ it("superdeno(app): should work with .send() on POST", (done) => {
+ const app = express();
+
+ app.use(json());
+
+ app.post("/", (req, res) => {
+ res.send(req.body.name);
+ });
+
+ superdeno(app)
+ .post("/")
+ .send({ name: "john" })
+ .expect("john", done);
+ });
+
+ it("superdeno(app): should handle headers correctly", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.cookie("foo", "bar");
+ res.cookie("user", "deno");
+ res.append("Set-Cookie", "fizz=buzz");
+ res.set("X-Tested-With", "SuperDeno");
+ res.type("application/json");
+ res.send();
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("content-length", "0")
+ .expect("content-type", "application/json; charset=utf-8")
+ .expect("set-cookie", "foo=bar; Path=/,user=deno; Path=/,fizz=buzz")
+ .expect("x-powered-by", "Express")
+ .expect("x-tested-with", "SuperDeno")
+ .expect(200, done);
+ });
+
+ it("superdeno(app): should work when unbuffered", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("Hello");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Hello", done);
+ });
+
+ it("superdeno(app): should default redirects to 0", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.redirect("/login");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(302, done);
+ });
+
+ it("superdeno(app): promise form: should default redirects to 0", async () => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.redirect("/login");
+ });
+
+ await superdeno(app)
+ .get("/")
+ .expect(302);
+ });
+
+ it("superdeno(app): .redirects(n): should handle intermediate redirects", (done) => {
+ const app = express();
+
+ app.get("/login", (_req, res) => {
+ res.send("Login");
+ });
+
+ app.get("/redirect", (_req, res) => {
+ res.redirect("/login");
+ });
+
+ app.get("/", (_req, res) => {
+ res.redirect("/redirect");
+ });
+
+ superdeno(app)
+ .get("/")
+ .redirects(1)
+ .expect(302, done);
+ });
+
+ it("superdeno(app): .redirects(n): promise form: should handle intermediate redirects", async () => {
+ const app = express();
+
+ app.get("/login", (_req, res) => {
+ res.send("Login");
+ });
+
+ app.get("/redirect", (_req, res) => {
+ res.redirect("/login");
+ });
+
+ app.get("/", (_req, res) => {
+ res.redirect("/redirect");
+ });
+
+ await superdeno(app)
+ .get("/")
+ .redirects(1)
+ .expect(302);
+ });
+
+ it("superdeno(app): .redirects(n): should handle full redirects", (done) => {
+ const app = express();
+
+ app.get("/login", (_req, res) => {
+ res.send("Login");
+ });
+
+ app.get("/redirect", (_req, res) => {
+ res.redirect("/login");
+ });
+
+ app.get("/", (_req, res) => {
+ res.redirect("/redirect");
+ });
+
+ superdeno(app)
+ .get("/")
+ .redirects(2)
+ .end((_err, res) => {
+ expect(res).toBeDefined();
+ expect(res.status).toEqual(200);
+ expect(res.text).toEqual("Login");
+ done();
+ });
+ });
+
+ it("superdeno(app): .redirects(n): promise form: should handle full redirects", async () => {
+ const app = express();
+
+ app.get("/login", (_req, res) => {
+ res.send("Login");
+ });
+
+ app.get("/redirect", (_req, res) => {
+ res.redirect("/login");
+ });
+
+ app.get("/", (_req, res) => {
+ res.redirect("/redirect");
+ });
+
+ const res = await superdeno(app)
+ .get("/")
+ .redirects(2);
+
+ expect(res).toBeDefined();
+ expect(res.status).toEqual(200);
+ expect(res.text).toEqual("Login");
+ });
+
+ // TODO: figure out the equivalent error scenario for Deno setup
+ // it('should handle socket errors', (done) => {
+ // const app = express();
+
+ // app.get('/', (req, res) => {
+ // res.destroy();
+ // });
+
+ // superdeno(app)
+ // .get('/')
+ // .end((err) => {
+ // expect(err).toBeDefined();
+ // done();
+ // });
+ // });
+
+ describe(".end(fn)", () => {
+ it("superdeno(app): .end(fn): should close server", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("superdeno FTW!");
+ });
+
+ let doneCount = 0;
+
+ superdeno(app)
+ .get("/")
+ .end(() => {
+ doneCount++;
+
+ if (doneCount === 2) {
+ done();
+ }
+ });
+
+ app.on("close", () => {
+ doneCount++;
+
+ if (doneCount === 2) {
+ done();
+ }
+ });
+ });
+
+ it("superdeno(app): .end(fn): should wait for server to close before invoking fn", (done) => {
+ const app = express();
+ let closed = false;
+
+ app.get("/", (_req, res) => {
+ res.send("superdeno FTW!");
+ });
+
+ superdeno(app)
+ .get("/")
+ .end(() => {
+ expect(closed).toBeTruthy();
+ done();
+ });
+
+ app.on("close", () => {
+ closed = true;
+ });
+ });
+
+ it("superdeno(app): .end(fn): should support nested requests", (done) => {
+ const app = express();
+ const test = superdeno(app);
+
+ app.get("/", (_req, res) => {
+ res.send("superdeno FTW!");
+ });
+
+ test
+ .get("/")
+ .end(() => {
+ test
+ .get("/")
+ .end((err, res) => {
+ expect(err).toBeNull();
+ expect(res.status).toEqual(200);
+ expect(res.text).toEqual("superdeno FTW!");
+ done();
+ });
+ });
+ });
+
+ it("superdeno(app): .end(fn): should include the response in the error callback", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("whatever");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(() => {
+ throw new Error("Some error");
+ })
+ .end((err, res) => {
+ expect(err).toBeDefined();
+ expect(res).toBeDefined();
+ // Duck-typing response, just in case.
+ expect(res.status).toEqual(200);
+ done();
+ });
+ });
+
+ it("superdeno(app): .end(fn): should set `this` to the test object when calling the error callback", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("whatever");
+ });
+
+ const test = superdeno(app).get("/");
+
+ test.expect(() => {
+ throw new Error("Some error");
+ }).end(function (this: Test, err, _res) {
+ expect(err).toBeDefined();
+ expect(this).toEqual(test);
+ done();
+ });
+ });
+
+ it("superdeno(app): .end(fn): should handle an undefined Response", (done) => {
+ const app = express();
+
+ let timeoutPromise: Promise;
+
+ app.get("/", async (_req, res) => {
+ timeoutPromise = new Promise((resolve) => {
+ setTimeout(async () => {
+ try {
+ await res.send();
+ } catch (_) {
+ // swallow
+ }
+
+ resolve(true);
+ }, 20);
+ });
+
+ await timeoutPromise;
+ });
+
+ const server = app.listen();
+ const address = server.address();
+ const url = `http://localhost:${address.port}`;
+
+ superdeno(url).get("/").timeout(1)
+ .expect(200, async (err, _res) => {
+ expect(err).toBeInstanceOf(Error);
+ server.close();
+ await timeoutPromise;
+ done();
+ });
+ });
+ });
+
+ describe(".expect(status[, fn])", () => {
+ it("superdeno(app): .expect(status[, fn]): should assert the response status", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(404)
+ .end((err, _res) => {
+ expect(err.message).toEqual('expected 404 "Not Found", got 200 "OK"');
+ done();
+ });
+ });
+ });
+
+ describe(".expect(status)", () => {
+ it("superdeno(app): .expect(status): should assert only status", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(200)
+ .end(done);
+ });
+
+ it("superdeno(app): .expect(status): should assert only error status'", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.sendStatus(400);
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(400)
+ .end(done);
+ });
+ });
+
+ describe(".expect(status, body[, fn])", () => {
+ it("superdeno(app): .expect(status, body[, fn]): should assert the response body and status", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("foo");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(200, "foo", done);
+ });
+
+ it("superdeno(app): .expect(status, body[, fn]): should assert the response body and error status'", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.status(400).send("foo");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(400, "foo", done);
+ });
+
+ describe("when the body argument is an empty string", () => {
+ it("superdeno(app): .expect(status, body[, fn]): should not quietly pass on failure", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("foo");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(200, "")
+ .end((err, _res) => {
+ expect(err.message).toEqual('expected "" response body, got "foo"');
+ done();
+ });
+ });
+ });
+ });
+
+ describe(".expect(body[, fn])", () => {
+ it("superdeno(app): .expect(body[, fn]): should assert the response body", (done) => {
+ const app = express();
+
+ app.set("json spaces", 0);
+
+ app.get("/", (_req, res) => {
+ res.send({ foo: "bar" });
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("hey")
+ .end((err, _res) => {
+ expect(err.message).toEqual(
+ 'expected "hey" response body, got \'{"foo":"bar"}\'',
+ );
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should assert the status before the body", (done) => {
+ const app = express();
+
+ app.set("json spaces", 0);
+
+ app.get("/", (_req, res) => {
+ res.status(500).send({ message: "something went wrong" });
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(200)
+ .expect("hey")
+ .end((err, _res) => {
+ expect(err.message).toEqual(
+ 'expected 200 "OK", got 500 "Internal Server Error"',
+ );
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should assert the response text", (done) => {
+ const app = express();
+
+ app.set("json spaces", 0);
+
+ app.get("/", (_req, res) => {
+ res.send({ foo: "bar" });
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect('{"foo":"bar"}', done);
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should assert the parsed response body", (done) => {
+ const app = express();
+
+ app.set("json spaces", 0);
+
+ app.get("/", (_req, res) => {
+ res.send({ foo: "bar" });
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect({ foo: "baz" })
+ .end((err, _res) => {
+ expect(err.message).toEqual(
+ 'expected { foo: "baz" } response body, got { foo: "bar" }',
+ );
+
+ superdeno(app)
+ .get("/")
+ .expect({ foo: "bar" })
+ .end(done);
+ });
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should test response object types", (done) => {
+ const app = express();
+ app.get("/", (_req, res) => {
+ res.status(200).json({ stringValue: "foo", numberValue: 3 });
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect({ stringValue: "foo", numberValue: 3 }, done);
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should deep test response object types", (done) => {
+ const app = express();
+ app.get("/", (_req, res) => {
+ res.status(200)
+ .json(
+ {
+ stringValue: "foo",
+ numberValue: 3,
+ nestedObject: { innerString: "5" },
+ },
+ );
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(
+ {
+ stringValue: "foo",
+ numberValue: 3,
+ nestedObject: { innerString: 5 },
+ },
+ )
+ .end((err, _res) => {
+ expect(err.message).toEqual(
+ 'expected {\n stringValue: "foo",\n numberValue: 3,\n nestedObject: { innerString: 5 }\n} response body, got {\n stringValue: "foo",\n numberValue: 3,\n nestedObject: { innerString: "5" }\n}',
+ ); // eslint-disable-line max-len
+
+ superdeno(app)
+ .get("/")
+ .expect(
+ {
+ stringValue: "foo",
+ numberValue: 3,
+ nestedObject: { innerString: "5" },
+ },
+ )
+ .end(done);
+ });
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should support regular expressions", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("foobar");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(/^bar/)
+ .end((err, _res) => {
+ expect(err.message).toEqual('expected body "foobar" to match /^bar/');
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should assert response body multiple times", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey deno");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(/deno/)
+ .expect("hey")
+ .expect("hey deno")
+ .end((err, _res) => {
+ expect(err.message).toEqual(
+ 'expected "hey" response body, got "hey deno"',
+ );
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(body[, fn]): should assert response body multiple times with no exception", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey deno");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect(/deno/)
+ .expect(/^hey/)
+ .expect("hey deno", done);
+ });
+ });
+
+ describe(".expect(field, value[, fn])", () => {
+ it("superdeno(app): .expect(field, value[, fn]): should assert the header field presence", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send({ foo: "bar" });
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Foo", "bar")
+ .end((err, _res) => {
+ expect(err.message).toEqual('expected "Content-Foo" header field');
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): should assert the header field value", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send({ foo: "bar" });
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Type", "text/html")
+ .end((err, _res) => {
+ expect(err.message).toEqual(
+ 'expected "Content-Type" of "text/html", ' +
+ 'got "application/json; charset=utf-8"',
+ );
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): should assert multiple fields", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Type", "text/html; charset=utf-8")
+ .expect("Content-Length", "3")
+ .end(done);
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): should support regular expressions", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Type", /^application/)
+ .end((err) => {
+ expect(err.message).toEqual(
+ 'expected "Content-Type" matching /^application/, ' +
+ 'got "text/html; charset=utf-8"',
+ );
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): should support numbers", (done) => {
+ const app = express();
+
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Length", 4)
+ .end((err) => {
+ expect(err.message).toEqual(
+ 'expected "Content-Length" of "4", got "3"',
+ );
+ done();
+ });
+ });
+
+ describe("handling arbitrary expect functions", () => {
+ const app = express();
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): reports errors", (done) => {
+ superdeno(app).get("/")
+ .expect((_res) => {
+ throw new Error("failed");
+ })
+ .end((err) => {
+ expect(err.message).toEqual("failed");
+ done();
+ });
+ });
+
+ it(
+ "superdeno(app): .expect(field, value[, fn]): ensures truthy non-errors returned from asserts are not promoted to errors",
+ (done) => {
+ superdeno(app).get("/")
+ .expect((_res) => {
+ return "some descriptive error";
+ })
+ .end((err) => {
+ expect(err).toBeNull();
+ done();
+ });
+ },
+ );
+
+ it("superdeno(app): .expect(field, value[, fn]): ensures truthy errors returned from asserts are throw to end", (done) => {
+ superdeno(app).get("/")
+ .expect((_res) => {
+ return new Error("some descriptive error");
+ })
+ .end((err) => {
+ expect(err.message).toEqual("some descriptive error");
+ expect(err).toBeInstanceOf(Error);
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): doesn't create false negatives", (done) => {
+ superdeno(app).get("/")
+ .expect((_res) => {
+ })
+ .end(done);
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): handles multiple asserts", (done) => {
+ const calls: number[] = [];
+
+ superdeno(app).get("/")
+ .expect((_res) => {
+ calls[0] = 1;
+ })
+ .expect((_res) => {
+ calls[1] = 1;
+ })
+ .expect((_res) => {
+ calls[2] = 1;
+ })
+ .end(() => {
+ const callCount = [0, 1, 2].reduce((count, i) => {
+ return count + calls[i];
+ }, 0);
+ expect(callCount).toEqual(3);
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): plays well with normal assertions - no false positives", (done) => {
+ superdeno(app).get("/")
+ .expect((_res) => {
+ })
+ .expect("Content-Type", /json/)
+ .end((err) => {
+ expect(err.message).toMatch(/Content-Type/);
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): plays well with normal assertions - no false negatives", (done) => {
+ superdeno(app).get("/")
+ .expect((_res) => {
+ })
+ .expect("Content-Type", /html/)
+ .expect((_res) => {
+ })
+ .expect("Content-Type", /text/)
+ .end(done);
+ });
+ });
+
+ describe("handling multiple assertions per field", () => {
+ it("superdeno(app): .expect(field, value[, fn]): should work", (done) => {
+ const app = express();
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Type", /text/)
+ .expect("Content-Type", /html/)
+ .end(done);
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): should return an error if the first one fails", (done) => {
+ const app = express();
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Type", /bloop/)
+ .expect("Content-Type", /html/)
+ .end((err) => {
+ expect(err.message).toEqual(
+ 'expected "Content-Type" matching /bloop/, ' +
+ 'got "text/html; charset=utf-8"',
+ );
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): should return an error if a middle one fails", (done) => {
+ const app = express();
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Type", /text/)
+ .expect("Content-Type", /bloop/)
+ .expect("Content-Type", /html/)
+ .end((err) => {
+ expect(err.message).toEqual(
+ 'expected "Content-Type" matching /bloop/, ' +
+ 'got "text/html; charset=utf-8"',
+ );
+ done();
+ });
+ });
+
+ it("superdeno(app): .expect(field, value[, fn]): should return an error if the last one fails", (done) => {
+ const app = express();
+ app.get("/", (_req, res) => {
+ res.send("hey");
+ });
+
+ superdeno(app)
+ .get("/")
+ .expect("Content-Type", /text/)
+ .expect("Content-Type", /html/)
+ .expect("Content-Type", /bloop/)
+ .end((err) => {
+ expect(err.message).toEqual(
+ 'expected "Content-Type" matching /bloop/, ' +
+ 'got "text/html; charset=utf-8"',
+ );
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/test/superdeno.oak.test.ts b/test/superdeno.oak.test.ts
index dac0dc8..d1f7387 100644
--- a/test/superdeno.oak.test.ts
+++ b/test/superdeno.oak.test.ts
@@ -1,15 +1,12 @@
// deno-lint-ignore-file no-explicit-any
-import { expect, getFreePort, Oak } from "./deps.ts";
-import { describe, it } from "./utils.ts";
+import { getFreePort } from "../deps.ts";
+import { expect, Oak } from "./deps.ts";
+import { describe, it, random } from "./utils.ts";
import { superdeno, Test } from "../mod.ts";
const { Application, Router } = Oak;
-function random(min: number, max: number): number {
- return Math.round(Math.random() * (max - min)) + min;
-}
-
const bootstrapOakServerTest = async (
{ configureApp, assertionsDelegate, done }: {
configureApp: (
@@ -35,7 +32,7 @@ const bootstrapOakServerTest = async (
app.use(router.allowedMethods());
const controller = new AbortController();
- const { signal } = controller;
+ const signal = controller.signal;
const freePort = await getFreePort(random(1024, 49151));
app.addEventListener("listen", ({ hostname, port, secure }: any) => {
@@ -52,7 +49,7 @@ describe("Oak: superdeno(url)", () => {
it("Oak: superdeno(url): should support open `superdeno(url)` format for web frameworks such as Oak", async (done) => {
await bootstrapOakServerTest({
configureApp: ({ router }) => {
- router.get("/", (ctx: Oak.RouterContext) => {
+ router.get("/", (ctx) => {
ctx.response.body = "hello";
});
},
@@ -71,7 +68,7 @@ describe("Oak: superdeno(url)", () => {
it("Oak: superdeno(url): .expect(status, body[, fn]): should assert the response body and status", async (done) => {
await bootstrapOakServerTest({
configureApp: ({ router }) => {
- router.get("/", (ctx: Oak.RouterContext) => {
+ router.get("/", (ctx) => {
ctx.response.body = "foo";
});
},
@@ -89,7 +86,7 @@ describe("Oak: superdeno(url)", () => {
it("superdeno(app): .expect(status, body[, fn]): should assert the response body and error status'", async (done) => {
await bootstrapOakServerTest({
configureApp: ({ router }) => {
- router.get("/", (ctx: Oak.RouterContext) => {
+ router.get("/", (ctx) => {
ctx.throw(400, "foo");
});
},
@@ -109,7 +106,7 @@ describe("Oak: superdeno(url)", () => {
it("Oak: superdeno(url): .end(cb): should set `this` to the test object when calling the `cb` in `.end(cb)`", async (done) => {
await bootstrapOakServerTest({
configureApp: ({ router }) => {
- router.get("/", (ctx: Oak.RouterContext) => {
+ router.get("/", (ctx) => {
ctx.response.body = "hello";
});
},
@@ -133,7 +130,7 @@ describe("Oak: superdeno(app.handle)", () => {
const router = new Router();
const app = new Application();
- router.get("/", (ctx: Oak.RouterContext) => {
+ router.get("/", (ctx) => {
ctx.response.body = "Hello Deno!";
});
diff --git a/test/superdeno.opine.test.ts b/test/superdeno.opine.test.ts
index 2aa6f8a..db30990 100644
--- a/test/superdeno.opine.test.ts
+++ b/test/superdeno.opine.test.ts
@@ -659,7 +659,7 @@ describe("superdeno(app)", () => {
)
.end((err, _res) => {
expect(err.message).toEqual(
- 'expected { stringValue: "foo", numberValue: 3, nestedObject: { innerString: 5 } } response body, got { stringValue: "foo", numberValue: 3, nestedObject: { innerString: "5" } }',
+ 'expected {\n stringValue: "foo",\n numberValue: 3,\n nestedObject: { innerString: 5 }\n} response body, got {\n stringValue: "foo",\n numberValue: 3,\n nestedObject: { innerString: "5" }\n}',
); // eslint-disable-line max-len
superdeno(app)
diff --git a/test/utils.ts b/test/utils.ts
index 0fdc505..57b2137 100644
--- a/test/utils.ts
+++ b/test/utils.ts
@@ -3,6 +3,17 @@
*/
export const TEST_TIMEOUT = 3000;
+/**
+ * Random number generator for a given range.
+ *
+ * @param min Lower bound
+ * @param max Upper bound
+ * @returns A random int in the range
+ */
+export function random(min: number, max: number): number {
+ return Math.round(Math.random() * (max - min)) + min;
+}
+
/**
* A no-op _describe_ method.
*
diff --git a/version.ts b/version.ts
index 78ed505..cebf4c4 100644
--- a/version.ts
+++ b/version.ts
@@ -1,9 +1,9 @@
/**
* Version of SuperDeno.
*/
-export const VERSION = "4.8.0";
+export const VERSION = "4.9.0";
/**
* Supported versions of Deno.
*/
-export const DENO_SUPPORTED_VERSIONS = ["1.19.3"];
+export const DENO_SUPPORTED_VERSIONS = ["1.40.2"];
Parameters
@@ -209,7 +209,7 @@Const isString
Parameters
diff --git a/egg.json b/egg.json index 44d4eb0..1ac3c6e 100644 --- a/egg.json +++ b/egg.json @@ -1,7 +1,7 @@ { "name": "superdeno", "description": "HTTP assertions for Deno made easy via superagent.", - "version": "4.8.0", + "version": "4.9.0", "repository": "https://github.com/cmorten/superdeno", "stable": true, "checkFormat": false, diff --git a/src/test.ts b/src/test.ts index 5b7fa10..6857ba3 100644 --- a/src/test.ts +++ b/src/test.ts @@ -7,13 +7,29 @@ * - https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/supertest/index.d.ts */ -import type { ListenerLike, ServerLike } from "./types.ts"; -import { assertEquals, STATUS_TEXT } from "../deps.ts"; +import type { + ExpressListenerLike, + ExpressServerLike, + ListenerLike, + ServerLike, +} from "./types.ts"; +import { assertEquals, getFreePort, STATUS_TEXT, StatusCode } from "../deps.ts"; import { superagent } from "./superagent.ts"; import { close } from "./close.ts"; -import { isListener, isServer, isStdNativeServer, isString } from "./utils.ts"; +import { + isExpressListener, + isExpressServer, + isListener, + isServer, + isStdNativeServer, + isString, +} from "./utils.ts"; import { exposeSham } from "./xhrSham.js"; +export function random(min: number, max: number): number { + return Math.round(Math.random() * (max - min)) + min; +} + /** * Custom expectation checker. */ @@ -201,9 +217,11 @@ export class Test extends SuperRequest { #redirects: number; #redirectList: string[]; #server!: ServerLike; + #serverSetupPromise: Promise