jest
jest copied to clipboard
[Feature]: Make asymmetric matcher types more useful
🚀 Feature Proposal
Using Jest expect's asymmetric matchers (anything, arrayContaining, etc.) in typed contexts gets very annoying very fast. For example:
function expectTypedEqual<T>(a: T, b: T) { expect(a).toEqual(b) }
// type error:
// Argument of type 'number[]' is not assignable to parameter of type 'AsymmetricMatcher_2'.
// Property 'asymmetricMatch' is missing in type 'number[]' but required in type 'AsymmetricMatcher_2'.
expectTypedEqual([1, 2, 3], expect.arrayContaining(3))
This is in some sense correct: expect.arrayContaining(3) is not, in fact, a number[]. But in practice you almost always want to use it as a number[]! The proposal here is that we should lie about the types, and say it returns a number[], because that allows for greater type-safety in practice.
Motivation
Right now, most places where matchers get used in vanilla jest are untyped: for example toEqual takes (unknown, unknown). But it would be nice to support a move towards having more typed assertions as suggested in #13334; and this would also be useful for a first-party when as I just proposed in #13811. (In fact, I've mainly run into this when using an internal version of when.)
Example
No response
Pitch
This can't really be done anywhere else, but doing it in jest is just a matter of deciding the new types are better than the old ones, and adding them! This is a breaking change, but in practice it seems unlikely that many people are using the existing (unexported) types in a rich way.
There was somewhat similar attempt to "lie about the types" of asymmetric matchers in @types/jest (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/62831) and it was reverted (https://github.com/DefinitelyTyped/DefinitelyTyped/pull/63151).
This can't really be done anywhere else
I still think this can be done and should be done in separate package. See: https://github.com/facebook/jest/issues/13334#issuecomment-1356308238. Glad to help, but I don’t want to create this package by myself. Simply because I can’t see myself maintaining a package which I do not use.
the existing (unexported) types
What do you have in mind?
Thanks, I totally forgot we can override the types of expect like that. I'm using the following types; I don't know that this is big enough to bother publishing as a package anyway but others can copy-paste this:
declare module "@jest/expect" {
interface AsymmetricMatchers {
any<T>(sample: T): T extends (...args: Array<unknown>) => infer V ? V : T
anything(): any
arrayContaining<T>(sample: T): Array<T>
closeTo(sample: number, precision?: number): number
objectContaining<T extends Record<string | number | symbol, unknown>, U extends T>(sample: T): U
stringContaining(sample: string): string
stringMatching(sample: string | RegExp): string
}
}
That’s right.
Same with .toEqual(). You can simply augmentation the types instead of creating new expectTypedEqual() function.
For .toEqual it doesn't work! What we would want is to do something like
declare type Expect {
<T>(actual: T): Matchers<void, T> & ...
} & ...
declare interface Matchers<R, T> {
toEqual(expected: T): R
...
}
but the problem is we don't actually get access to T -- Matchers is just Matchers<R> and Expect looks more like
declare type Expect {
<T>(actual: T): Matchers<void> & ...
} & ...
so our Matchers methods can't get access to the type of the argument of expect. (And Expect isn't an interface so we can't even extend its type to change this, not to mention the fact that that would be fairly fragile.) Would it make sense to change the types in Jest so Matchers gets access to T, even if the types in Jest itself don't actually use it?
BTW, another use case for this for us is to make the matchers be typescript assertions, so that for example after you do expect(v).toBeDefined() TypeScript will know that v can't be undefined. Right now we can't even do that on our own custom matchers, again because we don't have access to T.
Good point. I think it is reasonable to bring back Matchers<R, T>. It was there not so long time ago: #12404. Also having T around would be useful for custom matchers in general.
So would you be up to opening a PR?
Sure can: #13848
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.