Kensio Software Blog »

@kensio/smartass type narrowing assertion function npm package

Hugh Grigg | Kensio Software | Sunday 7 Jun 2026

The @kensio/smartass package provides TypeScript-first test assertion functions with precise type narrowing. The assertion functions provide type information to TypeScript, which makes it easier to write straightforward readable tests with assistance from the type system.

Most JavaScript and TypeScript test frameworks provide assertions through APIs such as:

expect(user).toBeDefined();
expect(status).toBe("active");

These are readable and widely used, but they generally cannot communicate additional type information back to TypeScript. The assertion checks something at runtime, but the compiler is unable to follow the types through the fluent interface. This means that calling the assertion does not update the type information in the test.

Updating type information is what type narrowing means. The @kensio/smartass package does that via a TypeScript feature called assertion signatures.

This means that each assertion in a test provides more type information to TypeScript. This lets you avoid boilerplate type casting, as well as convert those type casts into runtime assertions so the test validation is stronger and clearer.

The basic idea

Consider a value that might be undefined:

const user: User | undefined = getUser();

A normal runtime check narrows the type:

if (!user) {
  throw new Error("User not found");
}

user.name;

TypeScript understands that user cannot be undefined after the check.

An assertion function allows us to package that inference of type information into a reusable function:

import { assertNonNullable } from "@kensio/smartass";

const user: User | undefined = getUser();

assertNonNullable(user);

user.name;

The assertion throws if the value is null or undefined, but it also tells TypeScript that user cannot be undefined. TypeScript uses that information to infer that user must therefore be an instance of the User type.

As a result, there is no need for optional chaining or non-null assertions afterwards. E.g. with vitest assertions:

const user: User | undefined = getUser();

expect(user?.name).toBe("foobar");

Due to the lack of type signatures in that fluent assertion chain, we have to use the null-chain operator ? for the rest of the test. In this example it’s trivial, but in more complex tests with larger object structures, it can lead to a lot of clutter in the test.

Moving the type inference into an explicit assertion function is also beneficial by leading to more detailed validation in the test:

import { assertNonNullable, assertIdentical } from "@kensio/smartass";

const user: User | undefined = getUser();

assertNonNullable(user);
assertIdentical(user.name, "foobar");

With this approach, if user is undefined, we get a clear assertion failure message. IDEs like WebStorm can also highlight the specific assertion that failed, and link to it from test output, which also speeds up debugging.

Why assertion signatures are useful

Many assertion libraries focus on runtime behaviour at the expense of the type system. They verify conditions and produce useful failure messages, but the compiler cannot usually use those assertions when analysing code. It seems like a shame to miss out on such a large potential benefit in TypeScript.

For example:

expect(user).toBeDefined();

user.name;

TypeScript still considers user to be User | undefined.

By contrast:

assertNonNullable(user);

user.name;

TypeScript knows user must be exactly User.

The assertion signature narrows the type, so IntelliSense, autocomplete and compile-time checking all benefit from the assertion.

This seems trivial with this tiny example, but other more advanced assertion functions in @kensio/smartass can provide precise type narrowing.

This becomes particularly useful when dealing with values coming from APIs, databases, configuration files or external systems where the runtime shape is often broader than the shape expected by the rest of the application.

Narrowing to specific values

Assertion signatures are not limited to null checking.

For example, assertOneOf() can narrow a value to a specific set of allowed values:

import { assertOneOf } from "@kensio/smartass";

const status = getStatus();

assertOneOf(status, [
  "pending",
  "active",
  "completed",
]);

// status is now:
// "pending" | "active" | "completed"

The assertOneOf() function doesn’t just tell TypeScript that the input is of type string. It narrows the type down as much as possible to the literal union type.

Array assertions

The package also includes assertions that provide stronger guarantees about arrays.

For example:

import { assertArrayNotEmpty } from "@kensio/smartass";

const users: User[] = getUsers();

assertArrayNotEmpty(users);

const firstUser = users[0];

After the assertion, TypeScript understands that the array contains at least one item, so there’s no need for the null-chaining operator ? on users[0].

You can also assert on specific numbers of items in an array with similar precise type narrowing:

import { assertArrayLength } from "@kensio/smartass";

const users: User[] = getUsers();

assertArrayLength(users, 3);

const thirdUser = users[2];

Again, the assertArrayLength() function is able to tell TypeScript that users[2] exists. TypeScript can then infer from there that users[2] must be of type User.

Runtime validation and type information together

One of the recurring themes in TypeScript codebases is that runtime validation and static typing are often treated as separate concerns. Runtime validation is used in tests to confirm expected behaviour, while the type system might be used to provide some haphazard assistance and linting.

Assertion functions with type narrowing assertion signatures bring those two concerns together in tests.

Instead of validating a value and then manually convincing TypeScript that the value is safe, the assertion function lets you get both benefits together in one line.

The primary goal is not to create another testing DSL but to provide small assertion functions that improve both runtime safety and type inference. The assertion functions can be used in any test framework such as Jest or Vitest.

At the time of writing, `` provides the following assertion functions, each of which has the most precise type narrowing possible:

Installing it

npm: https://www.npmjs.com/package/@kensio/smartass

GitHub: https://github.com/KensioSoftware/smartass