diff options
| author | Kitson Kelly <me@kitsonkelly.com> | 2019-01-16 13:57:40 +1100 |
|---|---|---|
| committer | Ryan Dahl <ry@tinyclouds.org> | 2019-01-15 21:57:40 -0500 |
| commit | 7cb7b24537cd3771f8d2a02bdae9bffc9c7fcbb8 (patch) | |
| tree | 2b1697f517124515ae6d9951a9f66ebc2caf3568 | |
| parent | 8dd76af9ab7caa6f92b8a89a7adc61504dc7d277 (diff) | |
Improve assert (denoland/deno_std#124)
Original: https://github.com/denoland/deno_std/commit/9a3eb207dcd1d032a3c3f7e60f8ee9c5e793f022
| -rw-r--r-- | testing/README.md | 91 | ||||
| -rw-r--r-- | testing/mod.ts | 88 | ||||
| -rw-r--r-- | testing/test.ts | 106 |
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); +}); |
