summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2019-01-16 13:57:40 +1100
committerRyan Dahl <ry@tinyclouds.org>2019-01-15 21:57:40 -0500
commit7cb7b24537cd3771f8d2a02bdae9bffc9c7fcbb8 (patch)
tree2b1697f517124515ae6d9951a9f66ebc2caf3568
parent8dd76af9ab7caa6f92b8a89a7adc61504dc7d277 (diff)
Improve assert (denoland/deno_std#124)
Original: https://github.com/denoland/deno_std/commit/9a3eb207dcd1d032a3c3f7e60f8ee9c5e793f022
-rw-r--r--testing/README.md91
-rw-r--r--testing/mod.ts88
-rw-r--r--testing/test.ts106
3 files changed, 273 insertions, 12 deletions
diff --git a/testing/README.md b/testing/README.md
index 70968e3c7..49a6526fe 100644
--- a/testing/README.md
+++ b/testing/README.md
@@ -1,14 +1,41 @@
# Testing
+This module provides a few basic utilities to make testing easier and
+consistent in Deno.
+
## Usage
+The module exports a `test` function which is the test harness in Deno. It
+accepts either a function (including async functions) or an object which
+contains a `name` property and a `fn` property. When running tests and
+outputting the results, the name of the past function is used, or if the
+object is passed, the `name` property is used to identify the test.
+
+The module also exports `assert`, `assertEqual`, and `equal`.
+
+`equal` is a deep comparision function, where `actual` and `expected` are
+compared deeply, and if they vary, `equal` returns `false`.
+
+The export `assert` is a function, but it is also decorated with other useful
+functions:
+
+- `assert()` - Expects a boolean value, throws if the value is `false`.
+- `assert.equal()` - Uses the `equal` comparison and throws if the `actual` and
+ `expected` are not equal.
+- `assert.strictEqual()` - Compares `actual` and `expected` strictly, therefore
+ for non-primitives the values must reference the same instance.
+- `assert.throws()` - Expects the passed `fn` to throw. If `fn` does not throw,
+ this function does. Also compares any errors thrown to an optional expected
+ `Error` class and checks that the error `.message` includes an optional
+ string.
+
+`assertEqual()` is the same as `assert.equal()` but maintained for backwards
+compatibility.
+
+Basic usage:
+
```ts
-import {
- test,
- assert,
- equal,
- assertEqual
-} from "https://deno.land/x/testing/mod.ts";
+import { test, assert, equal } from "https://deno.land/x/testing/mod.ts";
test({
name: "testing example",
@@ -17,8 +44,8 @@ test({
assert(!equal("hello", "world"));
assert(equal({ hello: "world" }, { hello: "world" }));
assert(!equal({ world: "hello" }, { hello: "world" }));
- assertEqual("world", "world");
- assertEqual({ hello: "world" }, { hello: "world" });
+ assert.equal("world", "world");
+ assert.equal({ hello: "world" }, { hello: "world" });
}
});
```
@@ -31,7 +58,51 @@ test(function example() {
assert(!equal("hello", "world"));
assert(equal({ hello: "world" }, { hello: "world" }));
assert(!equal({ world: "hello" }, { hello: "world" }));
- assertEqual("world", "world");
- assertEqual({ hello: "world" }, { hello: "world" });
+ assert.equal("world", "world");
+ assert.equal({ hello: "world" }, { hello: "world" });
+});
+```
+
+Using `assert.strictEqual()`:
+
+```ts
+test(function isStrictlyEqual() {
+ const a = {};
+ const b = a;
+ assert.strictEqual(a, b);
+});
+
+// This test fails
+test(function isNotStrictlyEqual() {
+ const a = {};
+ const b = {};
+ assert.strictEqual(a, b);
+});
+```
+
+Using `assert.throws()`:
+
+```ts
+test(function doesThrow() {
+ assert.throws(() => {
+ throw new TypeError("hello world!");
+ });
+ assert.throws(() => {
+ throw new TypeError("hello world!");
+ }, TypeError);
+ assert.throws(
+ () => {
+ throw new TypeError("hello world!");
+ },
+ TypeError,
+ "hello"
+ );
+});
+
+// This test will not pass
+test(function fails() {
+ assert.throws(() => {
+ console.log("Hello world");
+ });
});
```
diff --git a/testing/mod.ts b/testing/mod.ts
index 96fe3f11f..1de5f55bc 100644
--- a/testing/mod.ts
+++ b/testing/mod.ts
@@ -29,11 +29,95 @@ export function assertEqual(actual: unknown, expected: unknown, msg?: string) {
}
}
-export function assert(expr: boolean, msg = "") {
+interface Constructor {
+ new (...args: any[]): any;
+}
+
+interface Assert {
+ /** Make an assertion, if not true, then throw. */
+ (expr: boolean, msg?: string): void;
+
+ /** Make an assertion that `actual` and `expected` are equal, deeply. If not
+ * deeply equal, then throw.
+ */
+ equal(actual: unknown, expected: unknown, msg?: string): void;
+
+ /** Make an assertion that `actual` and `expected` are strictly equal. If
+ * not then throw.
+ */
+ strictEqual(actual: unknown, expected: unknown, msg?: string): void;
+
+ /** Executes a function, expecting it to throw. If it does not, then it
+ * throws. An error class and a string that should be included in the
+ * error message can also be asserted.
+ */
+ throws(fn: () => void, errorClass?: Constructor, msgIncludes?: string): void;
+}
+
+export const assert = function assert(expr: boolean, msg = "") {
if (!expr) {
throw new Error(msg);
}
-}
+} as Assert;
+
+assert.equal = assertEqual;
+assert.strictEqual = function strictEqual(actual, expected, msg = "") {
+ if (actual !== expected) {
+ let actualString: string;
+ let expectedString: string;
+ try {
+ actualString = String(actual);
+ } catch (e) {
+ actualString = "[Cannot display]";
+ }
+ try {
+ expectedString = String(expected);
+ } catch (e) {
+ expectedString = "[Cannot display]";
+ }
+ console.error(
+ "strictEqual failed. actual =",
+ actualString,
+ "expected =",
+ expectedString
+ );
+ if (!msg) {
+ msg = `actual: ${actualString} expected: ${expectedString}`;
+ }
+ throw new Error(msg);
+ }
+};
+assert.throws = function throws(
+ fn,
+ ErrorClass: Constructor,
+ msgIncludes = "",
+ msg = ""
+) {
+ let doesThrow = false;
+ try {
+ fn();
+ } catch (e) {
+ if (ErrorClass && !(Object.getPrototypeOf(e) === ErrorClass.prototype)) {
+ msg = `Expected error to be instance of "${ErrorClass.name}"${
+ msg ? `: ${msg}` : "."
+ }`;
+ throw new Error(msg);
+ }
+ if (msgIncludes) {
+ if (!e.message.includes(msgIncludes)) {
+ msg = `Expected error message to include "${msgIncludes}", but got "${
+ e.message
+ }"${msg ? `: ${msg}` : "."}`;
+ throw new Error(msg);
+ }
+ }
+ doesThrow = true;
+ }
+ if (!doesThrow) {
+ msg = `Expected function to throw${msg ? `: ${msg}` : "."}`;
+ throw new Error(msg);
+ }
+};
export function equal(c: unknown, d: unknown): boolean {
const seen = new Map();
diff --git a/testing/test.ts b/testing/test.ts
index 7012a6e47..34b2762b4 100644
--- a/testing/test.ts
+++ b/testing/test.ts
@@ -28,6 +28,7 @@ test(function testingAssertEqual() {
const a = Object.create(null);
a.b = "foo";
assertEqual(a, a);
+ assert(assert.equal === assertEqual);
});
test(function testingAssertEqualActualUncoercable() {
@@ -55,3 +56,108 @@ test(function testingAssertEqualExpectedUncoercable() {
}
assert(didThrow);
});
+
+test(function testingAssertStrictEqual() {
+ const a = {};
+ const b = a;
+ assert.strictEqual(a, b);
+});
+
+test(function testingAssertNotStrictEqual() {
+ let didThrow = false;
+ const a = {};
+ const b = {};
+ try {
+ assert.strictEqual(a, b);
+ } catch (e) {
+ assert(e.message === "actual: [object Object] expected: [object Object]");
+ didThrow = true;
+ }
+ assert(didThrow);
+});
+
+test(function testingDoesThrow() {
+ let count = 0;
+ assert.throws(() => {
+ count++;
+ throw new Error();
+ });
+ assert(count === 1);
+});
+
+test(function testingDoesNotThrow() {
+ let count = 0;
+ let didThrow = false;
+ try {
+ assert.throws(() => {
+ count++;
+ console.log("Hello world");
+ });
+ } catch (e) {
+ assert(e.message === "Expected function to throw.");
+ didThrow = true;
+ }
+ assert(count === 1);
+ assert(didThrow);
+});
+
+test(function testingThrowsErrorType() {
+ let count = 0;
+ assert.throws(() => {
+ count++;
+ throw new TypeError();
+ }, TypeError);
+ assert(count === 1);
+});
+
+test(function testingThrowsNotErrorType() {
+ let count = 0;
+ let didThrow = false;
+ try {
+ assert.throws(() => {
+ count++;
+ throw new TypeError();
+ }, RangeError);
+ } catch (e) {
+ assert(e.message === `Expected error to be instance of "RangeError".`);
+ didThrow = true;
+ }
+ assert(count === 1);
+ assert(didThrow);
+});
+
+test(function testingThrowsMsgIncludes() {
+ let count = 0;
+ assert.throws(
+ () => {
+ count++;
+ throw new TypeError("Hello world!");
+ },
+ TypeError,
+ "world"
+ );
+ assert(count === 1);
+});
+
+test(function testingThrowsMsgNotIncludes() {
+ let count = 0;
+ let didThrow = false;
+ try {
+ assert.throws(
+ () => {
+ count++;
+ throw new TypeError("Hello world!");
+ },
+ TypeError,
+ "foobar"
+ );
+ } catch (e) {
+ assert(
+ e.message ===
+ `Expected error message to include "foobar", but got "Hello world!".`
+ );
+ didThrow = true;
+ }
+ assert(count === 1);
+ assert(didThrow);
+});