Definitelytyped: Add support for TypeScript's Assertion Functions

Created on 21 Dec 2019  路  10Comments  路  Source: DefinitelyTyped/DefinitelyTyped

## 馃殌 Feature Proposal

Originally posted at: https://github.com/facebook/jest/issues/9146

Support TypeScript 3.7's new assertion functions.

Motivation

Jest should support this TypeScript language feature to make authoring tests simpler.
## Example

Let's say I have a function under test that returns a nullable value:

```ts
export interface Data {
anotherCoolProp: any
somethingNeat: any
}

export function gimmeData(): Data | null {
// Implementation details
// ...
}
```

Currently, testing the results requires null checks for every assertion:

```ts
test('gimme that data', () => {
const data = gimmeData()

expect(data).toBeTruthy()

expect(data!.anotherCoolProp).toEqual('coolio')
expect(data!.somethingNeat).toEqual('neato')
})

// or:
test('gimme that data', () => {
const data = gimmeData()

if (!data) {
throw new Error('Expected data to be non-null')
}

expect(data.anotherCoolProp).toEqual('coolio')
expect(data.somethingNeat).toEqual('neato')
})
```

The first uses the ! non-null operator, which opts out of type safety and can lead to confusing error reports. The second seems non-idiomatic: why not write expect(data).toBeTruthy()?

With TypeScript 3.7 you can now define Jest's global expect to behave this way:

ts declare function <T>expect(value: T): asserts value is NonNullable<T>

(Although typing w/ the Jest's chaining will be a bit more involved.)

Lots of people have worked on these types, but I will start with @sandersn to see what he thinks about this.

Most helpful comment

This would be a game changer, hope someone can make progress on this 馃挭

All 10 comments

This is exciting! Having to add type refinements in addition to test assertions is a pain.

This type definitions should be updated too:

@types/assert

@nicoabie, @LinusU

If you don't have enough time, I'll probably update your definition tomorrow.

Nice, absolutely!

I'm pretty sure this won't be properly possible right now, since the matchers are typed to return R:

/**
 * The `expect` function is used every time you want to test a value.
 * You will rarely call `expect` by itself.
 */
interface Expect {
    /**
     * The `expect` function is used every time you want to test a value.
     * You will rarely call `expect` by itself.
     *
     * @param actual The value to apply matchers against.
     */
    <T = any>(actual: T): JestMatchers<T>;

  ...
}

type JestMatchers<T> = JestMatchersShape<Matchers<void, T>, Matchers<Promise<void>, T>>;
interface Matchers<R, T = {}> {
  ...
}

expects T isn't R, so as far as I know it's not really doable without some restructuring(which iirc is meant to happen at some point?).

Actually I just realised that R has the value of void, so while the above is still technically correct, I think it's primarily a case of dropping R in favor of T (or just replace usage of R with T to make it less breaking), and then using assertion types?

If people are happy with that, I'm happy to make a PR :)

+1! What's the status here.

It's not clear how this could work given Jest's API. For example, what would be the type of toBeDefined? To boil it down to the essence, it seems like you'd need to be able to write something like

type Expect = <T>(x: T) => {
  toBeDefined(): asserts x is NonNullable<T>;
  // ... other methods
};

But TypeScript rejects the assertion type with "Cannot find parameter 'x'."

This would be a game changer, hope someone can make progress on this 馃挭

From what I can see the current expect API would need changes to TypeScript to support assertion functions, so I've been playing around with some alternative APIs that are possible right now. Like the current chaining API, it looks like currying is not possible either eg. expect(x)(toBeDefined()), however I've come up with this:

type Matcher<T, X extends T> = (val: T) => asserts val is X

declare function expect<T, X extends T>(val: T, matcher: Matcher<T, X>): asserts val is X

declare function toBeDefined<T>(): (val: T) => asserts val is NonNullable<T> 

// ========================= //

declare const x: number | undefined;

expect(x, toBeDefined())

x // number

Playground

If there's interest in this I can continue and publish a library that mirrors the current expect API but with the above shape. Lemme know your thoughts!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

csharpner picture csharpner  路  3Comments

ArtemZag picture ArtemZag  路  3Comments

JudeAlquiza picture JudeAlquiza  路  3Comments

JWT
svipas picture svipas  路  3Comments

Zzzen picture Zzzen  路  3Comments