diff --git a/src/lib/ErrorAssertion.ts b/src/lib/ErrorAssertion.ts new file mode 100644 index 00000000..9b02ee3f --- /dev/null +++ b/src/lib/ErrorAssertion.ts @@ -0,0 +1,150 @@ +import { AssertionError } from "assert"; + +import { Assertion } from "./Assertion"; + +export class ErrorAssertion extends Assertion { + + public constructor(actual: T) { + super(actual); + } + + /** + * Check if the error has exactly the passed error. + * + * @param message the message the error should contain + * @returns the assertion instance + */ + public toHaveMessage(message: string): this { + const error = new AssertionError({ + actual: this.actual.message, + expected: message, + message: `Expected error to have the message: ${message}` + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected error NOT to have the message: ${message}` + }); + + return this.execute({ + assertWhen: this.actual.message === message, + error, + invertedError + }); + } + + /** + * Check if the error has a message that starts with the provided fragment + * + * @param fragment the fragment the message should start with + * @returns the assertion instance + */ + public toHaveMessageStartingWith(fragment: string): this { + const error = new AssertionError({ + actual: this.actual.message, + message: `Expected error to have a message starting with: ${fragment}` + }); + const invertedError = new AssertionError({ + actual: this.actual.message, + message: `Expected error NOT to have a message starting with: ${fragment}` + }); + + return this.execute({ + assertWhen: this.actual.message.startsWith(fragment), + error, + invertedError + }); + } + + /** + * Check if the error has a message that contains the provided fragment + * + * @param fragment the fragment the message should contain + * @returns the assertion instance + */ + public toHaveMessageContaining(fragment: string): this { + const error = new AssertionError({ + actual: this.actual.message, + message: `Expected error to have a message containing: ${fragment}` + }); + const invertedError = new AssertionError({ + actual: this.actual.message, + message: `Expected error NOT to have a message containing: ${fragment}` + }); + + return this.execute({ + assertWhen: this.actual.message.includes(fragment), + error, + invertedError + }); + } + + /** + * Check if the error has a message that ends with the provided fragment + * + * @param fragment the fragment the message should end with + * @returns the assertion instance + */ + public toHaveMessageEndingWith(fragment: string): this { + const error = new AssertionError({ + actual: this.actual.message, + message: `Expected error to have a message ending with: ${fragment}` + }); + const invertedError = new AssertionError({ + actual: this.actual.message, + message: `Expected error NOT to have a message ending with: ${fragment}` + }); + + return this.execute({ + assertWhen: this.actual.message.endsWith(fragment), + error, + invertedError + }); + } + + /** + * Check if the error has a message taht matches the provided regular + * expression. + * + * @param regex the regular expression to match the error message + * @returns the assertion error + */ + public toHaveMessageMatching(regex: RegExp): this { + const error = new AssertionError({ + actual: this.actual.message, + message: `Expected the error message to match the regex <${regex.source}>` + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected the error message NOT to match the regex <${regex.source}>` + }); + + return this.execute({ + assertWhen: regex.test(this.actual.message), + error, + invertedError + }); + } + + /** + * Check if the name of the error is the passed name. + * + * @param name the name of the error + * @returns the assertion instance + */ + public toHaveName(name: string): this { + const error = new AssertionError({ + actual: this.actual.message, + message: `Expected the error name to be <${name}>` + }); + const invertedError = new AssertionError({ + actual: this.actual, + message: `Expected the error name NOT to be <${name}>` + }); + + return this.execute({ + assertWhen: this.actual.name === name, + error, + invertedError + }); + } +} diff --git a/src/lib/FunctionAssertion.ts b/src/lib/FunctionAssertion.ts index 62c8e413..f219154c 100644 --- a/src/lib/FunctionAssertion.ts +++ b/src/lib/FunctionAssertion.ts @@ -1,25 +1,16 @@ import { AssertionError } from "assert"; import { Assertion } from "./Assertion"; +import { ErrorAssertion } from "./ErrorAssertion"; +import { TypeFactory } from "./helpers/TypeFactories"; export type AnyFunction = (...args: any[]) => any; -function functionExecution(func: T): Error | undefined { - try { - func(); - return undefined; - } catch (error) { - return error instanceof Error - ? error - : Error(`The function threw something that is not an Error: ${error}`); - } +interface Class extends Function { + prototype: T; } -function assertion(error: E | undefined , expectedError: E): boolean { - return !!error - && error?.name === expectedError.name - && error?.message === expectedError.message; -} +const NoThrow = Symbol("NoThrow"); export class FunctionAssertion extends Assertion { @@ -27,30 +18,142 @@ export class FunctionAssertion extends Assertion { super(actual); } + private captureError(): unknown | typeof NoThrow { + try { + this.actual(); + return NoThrow; + } catch (error) { + return error; + } + } + /** - * Check if the value throws an error. + * Check if the function throws anything when called. * * @returns the assertion instance */ - public toThrowError(expectedError?: E): this { - const expected = expectedError || new Error(); - const errorExecution = functionExecution(this.actual); + public toThrow(): this { + const captured = this.captureError(); const error = new AssertionError({ - actual: this.actual, - expected, - message: `Expected to throw error <${expected.name}> with message <'${expected.message || ""}'>` + actual: captured, + message: "Expected the function to throw when called" }); const invertedError = new AssertionError({ - actual: this.actual, - message: `Expected value to NOT throw error <${expected.name}> with message <'${expected.message || ""}'>` + actual: captured, + message: "Expected the function NOT to throw when called" }); return this.execute({ - assertWhen: expectedError - ? assertion(errorExecution, expected) - : errorExecution instanceof Error, + assertWhen: captured !== NoThrow, + error, + invertedError + }); + } + + /** + * Check if the function throws an {@link Error}. If the `ErrorType` is passed, + * it also checks if the error is an instance of the specific type. + * + * @example + * ``` + * expect(throwingFunction) + * .toThrowError() + * .toHaveMessage("Oops! Something went wrong...") + * + * expect(myCustomFunction) + * .toThrowError(MyCustomError) + * .toHaveMessage("Something failed!"); + * ``` + * + * @param ErrorType optional error type constructor to check the thrown error + * against. If is not provided, it defaults to {@link Error} + * @returns a new {@link ErrorAssertion} to assert over the error + */ + public toThrowError(): ErrorAssertion; + public toThrowError(ExpectedType: Class): ErrorAssertion; + public toThrowError(ExpectedType?: Class): ErrorAssertion { + const captured = this.captureError(); + + if (captured === NoThrow) { + throw new AssertionError({ + actual: captured, + message: "Expected the function to throw when called" + }); + } + + const ErrorType = ExpectedType ?? Error; + const error = new AssertionError({ + actual: captured, + message: `Expected the function to throw an error instance of <${ErrorType.name}>` + }); + const invertedError = new AssertionError({ + actual: captured, + message: `Expected the function NOT to throw an error instance of <${ErrorType.name}>` + }); + + this.execute({ + assertWhen: captured instanceof ErrorType, + error, + invertedError + }); + + return new ErrorAssertion(captured as E); + } + + /** + * Check if the function throws a non-error value when called. Additionally, + * you can pass a {@link TypeFactory} in the second argument so the returned + * assertion is for the specific value type. Otherwise, a basic + * {@link Assertion Assertion} instance is returned. + * + * @example + * ``` + * expect(raiseValue) + * .toThrowValue() + * .toBeEqual(someValue); + * + * expect(raiseExitCode) + * .toThrowValue(TypeFactories.Number) + * .toBeNegative(); + * ``` + * + * @param expected the value the function is expected to throw + * @param typeFactory optional type factory to perform more specific + * assertions over the thrown value + * @returns the factory assertion or a basic assertion instance + */ + public toThrowValue>(typeFactory?: TypeFactory): A { + const captured = this.captureError(); + + if (captured === NoThrow) { + throw new AssertionError({ + actual: captured, + message: "Expected the function to throw a value" + }); + } + + const error = new AssertionError({ + actual: captured, + message: typeFactory + ? `Expected the function to throw a value of type "${typeFactory.typeName}"` + : "Expected the function to throw a value" + }); + const invertedError = new AssertionError({ + actual: captured, + message: typeFactory + ? `Expected the function NOT to throw a value of type "${typeFactory.typeName}"` + : "Expected the function NOT to throw a value" + }); + const isTypeMatch = typeFactory?.predicate(captured) ?? true; + + this.execute({ + assertWhen: captured !== NoThrow && isTypeMatch, error, invertedError }); + + return typeFactory?.predicate(captured) + ? new typeFactory.Factory(captured) + : new Assertion(captured) as A; } } diff --git a/src/lib/expect.ts b/src/lib/expect.ts index 16484d1e..108ec04e 100644 --- a/src/lib/expect.ts +++ b/src/lib/expect.ts @@ -2,6 +2,7 @@ import { ArrayAssertion } from "./ArrayAssertion"; import { Assertion } from "./Assertion"; import { BooleanAssertion } from "./BooleanAssertion"; import { DateAssertion } from "./DateAssertion"; +import { ErrorAssertion } from "./ErrorAssertion"; import { AnyFunction, FunctionAssertion } from "./FunctionAssertion"; import { isAnyFunction, isJSObject, isPromise } from "./helpers/guards"; import { NumberAssertion } from "./NumberAssertion"; @@ -17,9 +18,10 @@ export function expect(actual: T): BooleanAssertion; export function expect(actual: T): NumberAssertion; export function expect(actual: T): StringAssertion; export function expect(actual: T): DateAssertion; +export function expect(actual: T): ArrayAssertion>; export function expect>(actual: T): PromiseAssertion>; export function expect(actual: T): FunctionAssertion; -export function expect(actual: T): ArrayAssertion>; +export function expect(actual: T): ErrorAssertion; export function expect(actual: T): ObjectAssertion; export function expect(actual: T): Assertion; export function expect(actual: T) { @@ -29,14 +31,14 @@ export function expect(actual: T) { case "string": return new StringAssertion(actual); } - if (Array.isArray(actual)) { - return new ArrayAssertion(actual); - } - if (actual instanceof Date) { return new DateAssertion(actual); } + if (Array.isArray(actual)) { + return new ArrayAssertion(actual); + } + if (isPromise(actual)) { return new PromiseAssertion(actual); } @@ -45,8 +47,8 @@ export function expect(actual: T) { return new FunctionAssertion(actual); } - if (Array.isArray(actual)) { - return new ArrayAssertion(actual); + if (actual instanceof Error) { + return new ErrorAssertion(actual); } if (isJSObject(actual)) { diff --git a/test/lib/ErrorAssertion.test.ts b/test/lib/ErrorAssertion.test.ts new file mode 100644 index 00000000..946cd318 --- /dev/null +++ b/test/lib/ErrorAssertion.test.ts @@ -0,0 +1,172 @@ +import assert, { AssertionError } from "assert"; + +import { ErrorAssertion } from "../../src/lib/ErrorAssertion"; + +class CustomError extends Error { + + public constructor(message?: string) { + super(message); + + this.name = this.constructor.name; + Error.captureStackTrace(this, this.constructor); + Object.setPrototypeOf(this, CustomError.prototype); + } +} + +describe("[Unit] ErrorAssertion.test.ts", () => { + describe(".toHaveMessage", () => { + context("when the error message is equal to the passed text", () => { + it("returns the assertion instance", () => { + const test = new ErrorAssertion(new Error("foo")); + + assert.deepStrictEqual(test.toHaveMessage("foo"), test); + assert.throws(() => test.not.toHaveMessage("foo"), { + message: "Expected error NOT to have the message: foo", + name: AssertionError.name + }); + }); + }); + + context("when the error message is not equal to the passed text", () => { + it("throws and assertion error", () => { + const test = new ErrorAssertion(new Error("foo")); + + assert.throws(() => test.toHaveMessage("bar"), { + message: "Expected error to have the message: bar", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toHaveMessage("bar"), test); + }); + }); + }); + + describe(".toHaveMessageStartingWith", () => { + context("when the error message starts with the passed text", () => { + it("returns the assertion instance", () => { + const test = new ErrorAssertion(new Error("Something went wrong")); + + assert.deepStrictEqual(test.toHaveMessageStartingWith("Something went"), test); + assert.throws(() => test.not.toHaveMessageStartingWith("Something went"), { + message: "Expected error NOT to have a message starting with: Something went", + name: AssertionError.name + }); + }); + }); + + context("when the error message does not start with the passed text", () => { + it("throws and assertion error", () => { + const test = new ErrorAssertion(new Error("Something went wrong")); + + assert.throws(() => test.toHaveMessageStartingWith("went wrong"), { + message: "Expected error to have a message starting with: went wrong", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toHaveMessageStartingWith("went wrong"), test); + }); + }); + }); + + describe(".toHaveMessageContaining", () => { + context("when the error message contains the passed text", () => { + it("returns the assertion instance", () => { + const test = new ErrorAssertion(new Error("Something went wrong")); + + assert.deepStrictEqual(test.toHaveMessageContaining("went"), test); + assert.throws(() => test.not.toHaveMessageContaining("went"), { + message: "Expected error NOT to have a message containing: went", + name: AssertionError.name + }); + }); + }); + + context("when the error message does not contain the passed text", () => { + it("throws and assertion error", () => { + const test = new ErrorAssertion(new Error("Something went wrong")); + + assert.throws(() => test.toHaveMessageContaining("foo"), { + message: "Expected error to have a message containing: foo", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toHaveMessageContaining("foo"), test); + }); + }); + }); + + describe(".toHaveMessageEndingWith", () => { + context("when the error message ends with the passed text", () => { + it("returns the assertion instance", () => { + const test = new ErrorAssertion(new Error("Something went wrong")); + + assert.deepStrictEqual(test.toHaveMessageEndingWith("went wrong"), test); + assert.throws(() => test.not.toHaveMessageEndingWith("went wrong"), { + message: "Expected error NOT to have a message ending with: went wrong", + name: AssertionError.name + }); + }); + }); + + context("when the error message does not end with the passed text", () => { + it("throws and assertion error", () => { + const test = new ErrorAssertion(new Error("Something went wrong")); + + assert.throws(() => test.toHaveMessageEndingWith("Something"), { + message: "Expected error to have a message ending with: Something", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toHaveMessageEndingWith("Something"), test); + }); + }); + }); + + describe(".toHaveMessageMatching", () => { + context("when the error message matches the regular expression", () => { + it("returns the assertion instance", () => { + const test = new ErrorAssertion(new Error("123")); + + assert.deepStrictEqual(test.toHaveMessageMatching(/\d/g), test); + assert.throws(() => test.not.toHaveMessageMatching(/\d/g), { + message: "Expected the error message NOT to match the regex <\\d>", + name: AssertionError.name + }); + }); + }); + + context("when the error message does not matche the regular expression", () => { + it("throws and assertion error", () => { + const test = new ErrorAssertion(new Error("foo")); + + assert.throws(() => test.toHaveMessageMatching(/\d/g), { + message: "Expected the error message to match the regex <\\d>", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toHaveMessageMatching(/\d/g), test); + }); + }); + }); + + describe(".toHaveName", () => { + context("when the error name is equal to the passed text", () => { + it("returns the assertion instance", () => { + const test = new ErrorAssertion(new CustomError("foo")); + + assert.deepStrictEqual(test.toHaveName("CustomError"), test); + assert.throws(() => test.not.toHaveName("CustomError"), { + message: "Expected the error name NOT to be ", + name: AssertionError.name + }); + }); + }); + + context("when the error name is not equal to the passed text", () => { + it("throws an assertion error", () => { + const test = new ErrorAssertion(new CustomError("foo")); + + assert.throws(() => test.toHaveName("foo"), { + message: "Expected the error name to be ", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toHaveName("foo"), test); + }); + }); + }); +}); diff --git a/test/lib/FunctionAssertion.test.ts b/test/lib/FunctionAssertion.test.ts index 9ad99724..e60992dd 100644 --- a/test/lib/FunctionAssertion.test.ts +++ b/test/lib/FunctionAssertion.test.ts @@ -1,101 +1,233 @@ -import assert from "assert"; +import assert, { AssertionError } from "assert"; +import { Assertion } from "../../src/lib/Assertion"; +import { ErrorAssertion } from "../../src/lib/ErrorAssertion"; import { FunctionAssertion } from "../../src/lib/FunctionAssertion"; +import { TypeFactories } from "../../src/lib/helpers/TypeFactories"; +import { NumberAssertion } from "../../src/lib/NumberAssertion"; -class CustomError extends Error{ - override name = "CustomError"; - constructor(msg: string) { - super(msg); - } - } +class CustomError extends Error { + + public constructor(message?: string) { + super(message); -const testCustomErrorFunction = () => { throw new CustomError("Custom Error"); }; -const testErrorFunction = () => { throw new TypeError("Error"); }; -const testNormalFunction = () => { return 0; }; + this.name = this.constructor.name; + Error.captureStackTrace(this, this.constructor); + Object.setPrototypeOf(this, CustomError.prototype); + } +} describe("[Unit] FunctionAssertion.test.ts", () => { - describe(".toThrowException", () => { - context("when the value throws an exception", () => { - it("returns the assertion instance", () => { - const test = new FunctionAssertion(testErrorFunction); - - assert.deepStrictEqual(test.toThrowError(new TypeError("Error")), test); - assert.throws(() => test.not.toThrowError(new TypeError("Error")), { - message: "Expected value to NOT throw error with message <'Error'>", - name: "AssertionError" + describe(".toThrow", () => { + context("when the function throws", () => { + const variants = [ + -1, + "foo", + true, + null, + Error(), + new CustomError("bar") + ] as const; + + variants.forEach(error => { + it(`[${error}] returns the assertion instance`, () => { + const test = new FunctionAssertion(() => { + throw error; + }); + + assert.deepStrictEqual(test.toThrow(), test); + assert.throws(() => test.not.toThrow(), { + message: "Expected the function NOT to throw when called", + name: AssertionError.name + }); }); }); }); - context("when the value throws an exception, but no error is provided", () => { - it("returns the assertion instance", () => { - const test = new FunctionAssertion(testErrorFunction); + context("when the function does not throw", () => { + it("throws an assertion error", () => { + const test = new FunctionAssertion(() => undefined); - assert.deepStrictEqual(test.toThrowError(), test); - assert.throws(() => test.not.toThrowError(), { - message: "Expected value to NOT throw error with message <''>", - name: "AssertionError" + assert.throws(() => test.toThrow(), { + message: "Expected the function to throw when called", + name: AssertionError.name }); + assert.deepStrictEqual(test.not.toThrow(), test); }); }); + }); - context("when the value throws a custom error exception", () => { - it("returns the assertion instance", () => { - const test = new FunctionAssertion(testCustomErrorFunction); + describe(".toThrowError", () => { + context("when the function throws", () => { + context("and the error is an instance of the expected type", () => { + context("and the expected type is provided", () => { + const message = "Something went wrong..."; + const variants = [ + [Error, new Error(message)], + [TypeError, new TypeError(message)], + [CustomError, new CustomError(message)] + ] as const; + + variants.forEach(([ErrorType, error]) => { + it(`[${ErrorType.name}] returns an ErrorAssertion instance`, () => { + const test = new FunctionAssertion(() => { + throw error; + }); + + assert.deepStrictEqual(test.toThrowError(ErrorType), new ErrorAssertion(error)); + assert.throws(() => test.not.toThrowError(ErrorType), { + message: `Expected the function NOT to throw an error instance of <${ErrorType.name}>`, + name: AssertionError.name + }); + }); + }); + }); - assert.deepStrictEqual(test.toThrowError(new CustomError("Custom Error")), test); - assert.throws(() => test.not.toThrowError(new CustomError("Custom Error")), { - message: "Expected value to NOT throw error with message <'Custom Error'>", - name: "AssertionError" + context("and the expected type is not provided", () => { + it("defaults to Error and returns an ErrorAssertion instance", () => { + const error = Error("Something went wrong..."); + const test = new FunctionAssertion(() => { + throw error; + }); + + assert.deepStrictEqual(test.toThrowError(), new ErrorAssertion(error)); + assert.throws(() => test.not.toThrowError(), { + message: "Expected the function NOT to throw an error instance of ", + name: AssertionError.name + }); + }); }); }); - }); - context("when the value throws a custom error exception, but no error is provided", () => { - it("returns the assertion instance", () => { - const test = new FunctionAssertion(testCustomErrorFunction); + context("and the error is not an instance of the expected type", () => { + context("and the expected type is provided", () => { + it("throws an assertion error", () => { + const error = new CustomError("foo"); + const test = new FunctionAssertion(() => { + throw error; + }); + + assert.throws(() => test.toThrowError(RangeError), { + message: "Expected the function to throw an error instance of ", + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toThrowError(RangeError), new ErrorAssertion(error)); + }); + }); - assert.deepStrictEqual(test.toThrowError(), test); - assert.throws(() => test.not.toThrowError(), { - message: "Expected value to NOT throw error with message <''>", - name: "AssertionError" + context("and the expected type is not provided", () => { + it("defaults to Error and throws an assertion error", () => { + const error = new TypeError("foo"); + const test = new FunctionAssertion(() => { + // tslint:disable-next-line: no-string-throw + throw "somthing"; + }); + const test2 = new FunctionAssertion(() => { + throw error; + }); + + assert.throws(() => test.toThrowError(), { + message: "Expected the function to throw an error instance of ", + name: AssertionError.name + }); + assert.deepStrictEqual(test2.not.toThrowError(RangeError), new ErrorAssertion(error)); + }); }); }); }); - context("when the value does NOT throw error", () => { + context("when the function does not throw", () => { it("throws an assertion error", () => { - const test = new FunctionAssertion(testNormalFunction); - - assert.throws(() => test.toThrowError(new TypeError("Error")), { - message: "Expected to throw error with message <'Error'>", - name: "AssertionError" - }); - assert.deepStrictEqual(test.not.toThrowError(new TypeError("Error")), test); + const assertionError = { + message: "Expected the function to throw when called", + name: AssertionError.name + }; + const test = new FunctionAssertion(() => undefined); + + assert.throws(() => test.toThrowError(), assertionError); + assert.throws(() => test.toThrowError(CustomError), assertionError); + assert.throws(() => test.not.toThrowError(), assertionError); + assert.throws(() => test.not.toThrowError(CustomError), assertionError); }); }); + }); - context("when the value does throw error, but with a different message", () => { - it("throws an assertion error", () => { - const test = new FunctionAssertion(testErrorFunction); + describe(".toThrowValue", () => { + context("when the function throws", () => { + context("and the type factory is provided", () => { + context("and the type matches the factory predicate", () => { + it("returns the factory assertion instance", () => { + const test = new FunctionAssertion(() => { + throw 5; + }); + + assert.deepStrictEqual(test.toThrowValue(TypeFactories.Number), new NumberAssertion(5)); + assert.throws(() => test.not.toThrowValue(TypeFactories.Number), { + message: `Expected the function NOT to throw a value of type "${TypeFactories.Number.typeName}"`, + name: AssertionError.name + }); + }); + }); - assert.throws(() => test.toThrowError(new TypeError("Error 2")), { - message: "Expected to throw error with message <'Error 2'>", - name: "AssertionError" + context("and the type does not match the factory predicate", () => { + it("throws and assertion error", () => { + const test = new FunctionAssertion(() => { + throw 5; + }); + + assert.throws(() => test.toThrowValue(TypeFactories.String), { + message: `Expected the function to throw a value of type "${TypeFactories.String.typeName}"`, + name: AssertionError.name + }); + assert.deepStrictEqual(test.not.toThrowValue(TypeFactories.String), new Assertion(5)); + }); + }); + }); + + context("and the type factory is not provided", () => { + it("returns a basic assertion of the value", () => { + const test = new FunctionAssertion(() => { + throw 5; + }); + + assert.deepStrictEqual(test.toThrowValue(), new Assertion(5)); + assert.throws(() => test.not.toThrowValue(), { + message: "Expected the function NOT to throw a value", + name: AssertionError.name + }); }); - assert.deepStrictEqual(test.not.toThrowError(new TypeError("Error 2")), test); }); }); - context("when the value does throw error, but of a different type", () => { - it("throws an assertion error", () => { - const test = new FunctionAssertion(testErrorFunction); + context("when the function does not throw", () => { + context("and the type factory is provided", () => { + it("throws an assertion error", () => { + const test = new FunctionAssertion(() => 0); + + assert.throws(() => test.toThrowValue(TypeFactories.Number), { + message: "Expected the function to throw a value", + name: AssertionError.name + }); + assert.throws(() => test.not.toThrowValue(TypeFactories.Number), { + message: "Expected the function to throw a value", + name: AssertionError.name + }); + }); + }); - assert.throws(() => test.toThrowError(new RangeError("Error")), { - message: "Expected to throw error with message <'Error'>", - name: "AssertionError" + context("and the type factory is not provided", () => { + it("throws an assertion error", () => { + const test = new FunctionAssertion(() => 0); + + assert.throws(() => test.toThrowValue(), { + message: "Expected the function to throw a value", + name: AssertionError.name + }); + assert.throws(() => test.not.toThrowValue(), { + message: "Expected the function to throw a value", + name: AssertionError.name + }); }); - assert.deepStrictEqual(test.not.toThrowError(new RangeError("Error")), test); }); }); }); diff --git a/test/lib/expect.test.ts b/test/lib/expect.test.ts index a3fec144..45cf7a88 100644 --- a/test/lib/expect.test.ts +++ b/test/lib/expect.test.ts @@ -5,21 +5,25 @@ import { ArrayAssertion } from "../../src/lib/ArrayAssertion"; import { Assertion } from "../../src/lib/Assertion"; import { BooleanAssertion } from "../../src/lib/BooleanAssertion"; import { DateAssertion } from "../../src/lib/DateAssertion"; +import { ErrorAssertion } from "../../src/lib/ErrorAssertion"; import { FunctionAssertion } from "../../src/lib/FunctionAssertion"; import { NumberAssertion } from "../../src/lib/NumberAssertion"; import { ObjectAssertion } from "../../src/lib/ObjectAssertion"; import { PromiseAssertion } from "../../src/lib/PromiseAssertion"; import { StringAssertion } from "../../src/lib/StringAssertion"; -describe("[Unit] expect.test.ts", () => { - context("when the actual value is a promise", () => { - it("returns a PromiseAssertion instance", () => { - const test = expect(Promise.resolve("foo")); +class CustomError extends Error { - assert(test instanceof PromiseAssertion); - }); - }); + public constructor(message?: string) { + super(message); + this.name = this.constructor.name; + Error.captureStackTrace(this, this.constructor); + Object.setPrototypeOf(this, CustomError.prototype); + } +} + +describe("[Unit] expect.test.ts", () => { context("when the actual value is a boolean", () => { it("returns a BooleanAssertion instance", () => { const test = expect(true); @@ -28,7 +32,15 @@ describe("[Unit] expect.test.ts", () => { }); }); - context("when the actual value is a String", () => { + context("when the actual value is a number", () => { + it("returns a NumberAssertion instance", () => { + const test = expect(1); + + assert(test instanceof NumberAssertion); + }); + }); + + context("when the actual value is a string", () => { it("returns a StringAssertion instance", () => { const test = expect("Hello World!"); @@ -44,11 +56,19 @@ describe("[Unit] expect.test.ts", () => { }); }); - context("when the actual value is a Number", () => { - it("returns a NumberAssertion instance", () => { - const test = expect(1); + context("when the actual value is an array", () => { + it("returns an ArrayAssertion instance", () => { + const test = expect([1, 2, 3]); - assert(test instanceof NumberAssertion); + assert(test instanceof ArrayAssertion); + }); + }); + + context("when the actual value is a promise", () => { + it("returns a PromiseAssertion instance", () => { + const test = expect(Promise.resolve("foo")); + + assert(test instanceof PromiseAssertion); }); }); @@ -60,11 +80,17 @@ describe("[Unit] expect.test.ts", () => { }); }); - context("when the actual value is an array", () => { - it("returns an ArrayAssertion instance", () => { - const test = expect([1, 2, 3]); - - assert(test instanceof ArrayAssertion); + context("when the actual value is an error", () => { + [ + new Error("classic"), + new CustomError("custom") + ] + .forEach(error => { + it(`[${error.name}] returns an ErrorAssertion`, () => { + const test = expect(error); + + assert(test instanceof ErrorAssertion); + }); }); });