Expect API
TSTyche includes the expect
flavoured assertions to compare types.
Type Inference Caveats
If an expression is passed as the target or the source of an assertion, TSTyche will use the TypeScript’s type checker to infer its type. This section is a reminder of some aspects of the inference process (like type widening or excess property checks) that may be misinterpreted.
TSTyche sees exactly the same types which you see in code editor’s hover messages. To learn more, see the Introduction page.
Type Widening
In mutable context a literal type is inferred as its base type:
import { } from "tstyche";
const = { : 2, : [3, 3] };
()..<{ : number; : number[] }>();
To tell to the type checker that a type should not be widened, use type assertions :
import { } from "tstyche";
const = { : 6 as , : [9, 9] as 9[] };
()..<{ : 6; : 9[] }>();
It is also possible to use the const
type assertion to convert the entire object, but in this case a different type is created:
import { } from "tstyche";
const = { : 6, : [9, 9] } as ;
()..<{ readonly : 6; readonly : readonly [9, 9] }>();
Excess Property Checks
If passed as arguments, the object literals undergo the excess property checks :
import { } from "tstyche";
let : { ?: number } = {};
= { silent: true, : 800 };
// Just like above, object literal may only specify known properties
({ : true, : 800 })...<{ ?: number }>();
<{ ?: number }>()...({ : true, : 800 });
// But object types are allowed to have excess properties
<{ : true; : 800 }>()..<{ ?: number }>();
<{ ?: number }>()..<{ : true; : 800 }>();
Generally these failures prevent outdated assertions hanging around in your tests. If the excess property checks is not what you need, follow the above link to learn how to get around them. For example, you can pass a reference instead of an object literal:
import { } from "tstyche";
const = { : true, : 800 };
()..<{ ?: number }>();
expect()
The expect()
function builds an assertion. It must be followed by the .type
modifier and a matcher. Optionally, a run mode flag can be appended. expect()
can be nested inside code.
Signatures. The source type can be provided either as a type argument or inferred from an expression:
expect<Source>()
expect(source: unknown)
Run Mode Flags
To select assertions to run, append the run mode flags to the expect()
function. The flags are inherited from the parent.
.only
Marks an assertion as focused. When there are focused groups, tests or assertions in a file, only those will run.
.skip
Marks an assertion as skipped. The test runner will ignore anything skipped and will suppress any type errors raised by the expressions or types passed to the expect()
or a matcher function.
Matchers
A matcher is the final element in the assertion chain. It defines the condition to check.
.not
To negate a matcher, prepend .not
before it:
import { expect } from "tstyche";
expect<string>().type.toBe<string>();
expect<number>().type.not.toBe<string>();
.toAcceptProps()
Checks if the JSX component accepts the given props.
This is a work in progress feature. Generic components are not yet supported.
import { , } from "tstyche";
interface ButtonProps {
: string;
?: "reset" | "submit";
}
function ({ , }: ButtonProps) {
return < ={}>{}</>;
}
("accepts props?", () => {
()..({ : "Send" });
()..({ : "Clear", : "reset" as });
()...({ : "Download", : "button" as });
()...({});
});
Signatures. The props type can be provided either as a type argument or inferred from an expression:
.toAcceptProps<Target>()
.toAcceptProps(target: unknown)
.toBe()
Checks if the source type the same as the target type.
import { , } from "tstyche";
type = (...: any) => any;
type <> = keyof {
[ in keyof as <>[] extends ? : never]: [];
};
interface Sample {
: string;
: () => number;
?: () => number;
}
("MethodLikeKeys", () => {
<<Sample>>()..<"getLength" | "getWidth">();
});
Signatures. The target type can be provided either as a type argument or inferred from an expression:
.toBe<Target>()
.toBe(target: unknown)
.toBeApplicable
Checks if the decorator function can be applied to a class or a class member.
Differently from other matchers, .toBeApplicable
must be used as a decorator:
import { , } from "tstyche";
function <, extends (: , ...: any) => any>(
: ,
: <, >,
) {
// ...
}
("bind", () => {
class {
#name: string;
constructor(: string) {
this.#name = ;
}
@(()..) // <-- and here it is!
() {
return `Hello, my name is ${this.#name}.`;
}
}
});
.toBeAssignableTo()
Checks if the source type is assignable to the target type.
import { , } from "tstyche";
("Set", () => {
(new (["abc"]))..<<string>>();
(new ([123]))..<<number>>();
(new ([123, "abc"]))...<<string>>();
(new ([123, "abc"]))...<<number>>();
});
Use the .toBeAssignableWith()
matcher to test the relationship in the reverse direction.
As the best practice, always keep the type under test on the left hand side of the assertion. This makes the test easier to read:
- expect("abc").type.toBeAssignableTo<Awaitable<string>>();
+ expect<Awaitable<string>>().type.toBeAssignableWith("abc");
Signatures. The target type can be provided either as a type argument or inferred from an expression:
.toBeAssignableTo<Target>()
.toBeAssignableTo(target: unknown)
.toBeAssignableWith()
Checks if the source type is assignable with the target type.
import { , } from "tstyche";
type <> = | <>;
("Awaitable", () => {
<<string>>()..("abc");
<<string>>()..(.("abc"));
<<string>>()...(123);
<<string>>()...(.(123));
});
Use the .toBeAssignableTo()
matcher to test the relationship in the reverse direction.
As the best practice, always keep the type under test on the left hand side of the assertion. This makes the test easier to read:
- expect<Set<string>>.type.toBeAssignableWith(new Set(["abc"]));
+ expect(new Set(["abc"])).type.toBeAssignableTo<Set<string>>();
Signatures. The target type can be provided either as a type argument or inferred from an expression:
.toBeAssignableWith<Target>()
.toBeAssignableWith(target: unknown)
.toBeCallableWith()
Checks if a function is callable with the given arguments.
import { , } from "tstyche";
function < extends { : number }>(: , : ) {
return . === .;
}
("isSameLength", () => {
(([1, 2], [1, 2, 3]))..<boolean>();
(("one", "two"))..<boolean>();
()...(1, 2);
()...("zero", [123]);
(<string | <number>>)..("zero", [123]);
});
Signature. The matcher accepts zero or more arguments:
.toBeCallableWith(...args: Array<unknown>)
.toBeConstructableWith()
Checks if a class is constructable with the given arguments.
import { , } from "tstyche";
class <> {
: ;
: ;
constructor(: , : ) {
this. = ;
this. = ;
}
}
("Pair", () => {
()..("sun", "moon");
()..(true, false);
()...("five", 10);
(<number | string>)..("five", 10);
()...();
()...("nope");
});
Signature. The matcher accepts zero or more arguments:
.toBeConstructableWith(...args: Array<unknown>)
.toHaveProperty()
Checks if a property key exists on the source type.
import { , } from "tstyche";
type <> = {
[ in keyof as <, "setup" | "teardown">]: [];
};
interface Sample {
[: `data-${string}`]: string;
?: boolean | undefined;
: (: string, : number) => void;
: () => void;
: () => void;
}
("Worker", () => {
<<Sample>>()..("data-sample");
<<Sample>>()..("isBusy");
<<Sample>>()..("runTest");
<<Sample>>()...("setup");
<<Sample>>()...("teardown");
});
Signature. The key
argument can be a string, number or symbol:
.toHaveProperty(key: string | number | symbol)
.toRaiseError()
Checks if the source type raises an error.
import { , } from "tstyche";
interface <, = unknown> {
[: string]: (: ) => ;
}
("Matchers", () => {
<<void, string>>()...();
<<void>>()...();
<>()..("requires between 1 and 2 type arguments");
});
It is recommended to pass a substring of an error message to the matcher. Alternatively a numeric error code can be provided. If the source type raises several errors, the matcher will require an argument for each of them.
Avoid the // @ts-expect-error
comments in your type tests, instead always use .toRaiseError()
. The comment suppresses syntax errors, hence it turns typos into falsy passing tests. For example, the following is passing despite two syntax errors:
interface <, = unknown> {
[: string]: (: ) => ;
}
// @ts-expect-error: requires a type argument
type = <>;
Signature. The matcher accepts one or more substrings, numeric codes or regular expressions:
.toRaiseError(...target: Array<string | number | RegExp>)