Skip to content

Commit 48c43f0

Browse files
authored
feat(assertion): Add .toBeInstanceOf(Constructor) matcher (#57)
1 parent 3ec760a commit 48c43f0

File tree

5 files changed

+90
-9
lines changed

5 files changed

+90
-9
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ For a list of all matchers and extended documentation, please refer to the [API
9090

9191
You can find the full API reference [here](https://stackbuilders.github.io/assertive-ts/docs/build/)
9292

93+
## Coming Soon
94+
95+
- Extension mechanism ⚙️
96+
9397
## Contributors ✨
9498

9599
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

src/lib/Assertion.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { UnsupportedOperationError } from "./errors/UnsupportedOperationError";
55
import { isJSObject, isKeyOf } from "./helpers/guards";
66
import { TypeFactory } from "./helpers/TypeFactories";
77

8+
export interface Constructor<T> extends Function {
9+
prototype: T;
10+
}
11+
812
export interface ExecuteOptions {
913
/**
1014
* The condition for when the assertion should pass. The negation of this
@@ -217,6 +221,36 @@ export class Assertion<T> {
217221
});
218222
}
219223

224+
/**
225+
* Check if the value is an instance of the provided constructor.
226+
*
227+
* @example
228+
* ```
229+
* expect(pontiac).toBeInstanceOf(Car);
230+
*
231+
* expect(today).toBeInstanceOf(Date);
232+
* ```
233+
*
234+
* @param Expected the constructor the value should be an instance
235+
* @returns the assertion instance
236+
*/
237+
public toBeInstanceOf(Expected: Constructor<any>): this {
238+
const error = new AssertionError({
239+
actual: this.actual,
240+
message: `Expected value to be an instance of <${Expected.name}>`
241+
});
242+
const invertedError = new AssertionError({
243+
actual: this.actual,
244+
message: `Expected value NOT to be an instance of <${Expected.name}>`
245+
});
246+
247+
return this.execute({
248+
assertWhen: this.actual instanceof Expected,
249+
error,
250+
invertedError
251+
});
252+
}
253+
220254
/**
221255
* Check if the value is deep equal to another value.
222256
*

src/lib/FunctionAssertion.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
import { AssertionError } from "assert";
22

3-
import { Assertion } from "./Assertion";
3+
import { Assertion, Constructor } from "./Assertion";
44
import { ErrorAssertion } from "./ErrorAssertion";
55
import { TypeFactory } from "./helpers/TypeFactories";
66

77
export type AnyFunction = (...args: any[]) => any;
88

9-
export interface Class<T> extends Function {
10-
prototype: T;
11-
}
12-
139
const NoThrow = Symbol("NoThrow");
1410

1511
/**
@@ -76,8 +72,8 @@ export class FunctionAssertion<T extends AnyFunction> extends Assertion<T> {
7672
* @returns a new {@link ErrorAssertion} to assert over the error
7773
*/
7874
public toThrowError(): ErrorAssertion<Error>;
79-
public toThrowError<E extends Error>(ExpectedType: Class<E>): ErrorAssertion<E>;
80-
public toThrowError<E extends Error>(ExpectedType?: Class<E>): ErrorAssertion<E> {
75+
public toThrowError<E extends Error>(Expected: Constructor<E>): ErrorAssertion<E>;
76+
public toThrowError<E extends Error>(Expected?: Constructor<E>): ErrorAssertion<E> {
8177
const captured = this.captureError();
8278

8379
if (captured === NoThrow) {
@@ -87,7 +83,7 @@ export class FunctionAssertion<T extends AnyFunction> extends Assertion<T> {
8783
});
8884
}
8985

90-
const ErrorType = ExpectedType ?? Error;
86+
const ErrorType = Expected ?? Error;
9187
const error = new AssertionError({
9288
actual: captured,
9389
message: `Expected the function to throw an error instance of <${ErrorType.name}>`

src/lib/ObjectAssertion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { isDeepStrictEqual } from "util";
33

44
import { Assertion } from "./Assertion";
55

6-
export type JSObject = Record<keyof any, unknown>;
6+
export type JSObject = Record<keyof any, any>;
77

88
export type Entry<T, K = keyof T> = K extends keyof T
99
? [K, T[K]]

test/lib/Assertion.test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ const BASE_DIFFS = [
5252
["date", TODAY, new Date("2021-12-10T00:00:00.001Z")]
5353
];
5454

55+
class Car {
56+
57+
private readonly model: string;
58+
59+
constructor(model: string) {
60+
this.model = model;
61+
}
62+
63+
public showModel(): string {
64+
return this.model;
65+
}
66+
}
67+
5568
function truthyAsText(value: typeof TRUTHY_VALUES[number]): string {
5669
if (Array.isArray(value) && value.length === 0) {
5770
return "Empty array";
@@ -263,6 +276,40 @@ describe("[Unit] Assertion.test.ts", () => {
263276
});
264277
});
265278

279+
describe(".toBeInstanceOf", () => {
280+
context("when the value is an instance of the constructor", () => {
281+
const variants = [
282+
[new Date(), Date],
283+
[new Error("failed!"), Error],
284+
[new Car("Pontiac GT-37"), Car]
285+
] as const;
286+
287+
variants.forEach(([value, Constructor]) => {
288+
it(`[Instance: ${Constructor.name}]: returns the assertion instance`, () => {
289+
const test = new Assertion(value);
290+
291+
assert.deepStrictEqual(test.toBeInstanceOf(Constructor), test);
292+
assert.throws(() => test.not.toBeInstanceOf(Constructor), {
293+
message: `Expected value NOT to be an instance of <${Constructor.name}>`,
294+
name: AssertionError.name
295+
});
296+
});
297+
});
298+
});
299+
300+
context("when the value in not an instance of the constructor", () => {
301+
it("throws an assertion error", () => {
302+
const test = new Assertion(new Date());
303+
304+
assert.throws(() => test.toBeInstanceOf(Car), {
305+
message: "Expected value to be an instance of <Car>",
306+
name: AssertionError.name
307+
});
308+
assert.deepStrictEqual(test.not.toBeInstanceOf(Car), test);
309+
});
310+
});
311+
});
312+
266313
describe(".toBeEqual", () => {
267314
context("when the value is referentially, shallow, and deep equal", () => {
268315
[

0 commit comments

Comments
 (0)