Repository: jestjs/jest (2025-07-02T13:53:19Z)

Issue:
[Bug]: .resolves matcher does not accept a function returning a promise (inconsistent with .rejects)
### Version

30.0.3

### Steps to reproduce

1. Clone https://github.com/Woodz/repro/tree/jest/bug-15713-asymmetry-between-resolves-and-rejects
1. Run `yarn install`
1. Run `yarn test`

### Expected behavior

For symmetry, I would expect .resolves to accept a function returning a promise (i.e. `expect(() => asyncFn()).resolves`) and internally unwrap it, just like .rejects does.

### Actual behavior

Jest’s .rejects matcher accepts both:

A Promise: `expect(promise).rejects.toThrow()`

A function returning a Promise: `expect(() => promise).rejects.toThrow()`

However, .resolves only accepts a Promise:

```
await expect(myAsyncFn()).resolves.toBe(123); // ✅ works
await expect(() => myAsyncFn()).resolves.toBe(123); // ❌ Matcher error: received value must be a promise
```
This inconsistency is surprising because .rejects supports both use cases — likely to handle .toThrow() cleanly — but .resolves does not.


### Additional context

_No response_

### Environment

```shell
System:
    OS: macOS 15.1
    CPU: (8) arm64 Apple M2
  Binaries:
    Node: 22.14.0 - ~/.nvm/versions/node/v22.14.0/bin/node
    Yarn: 1.22.22 - /opt/homebrew/bin/yarn
    npm: 10.9.2 - ~/.nvm/versions/node/v22.14.0/bin/npm
  npmPackages:
    jest: ^30.0.3 => 30.0.3
```


Discussion:
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](https://stackoverflow.com/questions/tagged/jestjs) or our [discord channel](https://discord.gg/j6FKKQQrW9) for questions.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bee6c464032b..32089fccb53d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,8 @@
 
 ### Features
 
-- `[expect]` Have `Inverse` exportable ([#15704](https://github.com/jestjs/jest/pull/15704))
+- `[expect]` Have `Inverse` exportable ([#15714](https://github.com/jestjs/jest/pull/15714))
+- `[expect]` feat: support `async functions` in `toBe` ([#15704](https://github.com/jestjs/jest/pull/15704))
 - `[jest-snapshot]` Handle line endings in snapshots ([#15708](https://github.com/jestjs/jest/pull/15708))
 
 ## 30.0.3
diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts
index 934d5711a3bd..1228cd4cfbe5 100644
--- a/packages/expect/src/index.ts
+++ b/packages/expect/src/index.ts
@@ -165,7 +165,7 @@ const makeResolveMatcher =
     matcherName: string,
     matcher: RawMatcherFn,
     isNot: boolean,
-    actual: Promise<any>,
+    actual: Promise<any> | (() => Promise<any>),
     outerErr: JestAssertionError,
   ): PromiseMatcherFn =>
   (...args) => {
@@ -174,11 +174,16 @@ const makeResolveMatcher =
       promise: 'resolves',
     };
 
-    if (!isPromise(actual)) {
+    const actualWrapper: Promise<any> =
+      typeof actual === 'function' ? actual() : actual;
+
+    if (!isPromise(actualWrapper)) {
       throw new JestAssertionError(
         matcherUtils.matcherErrorMessage(
           matcherUtils.matcherHint(matcherName, undefined, '', options),
-          `${matcherUtils.RECEIVED_COLOR('received')} value must be a promise`,
+          `${matcherUtils.RECEIVED_COLOR(
+            'received',
+          )} value must be a promise or a function returning a promise`,
           matcherUtils.printWithType(
             'Received',
             actual,
@@ -190,7 +195,7 @@ const makeResolveMatcher =
 
     const innerErr = new JestAssertionError();
 
-    return actual.then(
+    return actualWrapper.then(
       result =>
         makeThrowingMatcher(matcher, isNot, 'resolves', result, innerErr).apply(
           null,


Test patch:
diff --git a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
index b710e48c1aae..7c8e4af67547 100644
--- a/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
+++ b/packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
@@ -87,7 +87,7 @@ Rejected to value: <r>4</>
 exports[`.resolves fails non-promise value "a" 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  string
 Received has value: <r>"a"</>
@@ -96,7 +96,7 @@ Received has value: <r>"a"</>
 exports[`.resolves fails non-promise value "a" synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  string
 Received has value: <r>"a"</>
@@ -105,7 +105,7 @@ Received has value: <r>"a"</>
 exports[`.resolves fails non-promise value [1] 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  array
 Received has value: <r>[1]</>
@@ -114,7 +114,7 @@ Received has value: <r>[1]</>
 exports[`.resolves fails non-promise value [1] synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  array
 Received has value: <r>[1]</>
@@ -123,7 +123,7 @@ Received has value: <r>[1]</>
 exports[`.resolves fails non-promise value [Function anonymous] 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  function
 Received has value: <r>[Function anonymous]</>
@@ -132,7 +132,7 @@ Received has value: <r>[Function anonymous]</>
 exports[`.resolves fails non-promise value [Function anonymous] synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  function
 Received has value: <r>[Function anonymous]</>
@@ -141,7 +141,7 @@ Received has value: <r>[Function anonymous]</>
 exports[`.resolves fails non-promise value {"a": 1} 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  object
 Received has value: <r>{"a": 1}</>
@@ -150,7 +150,7 @@ Received has value: <r>{"a": 1}</>
 exports[`.resolves fails non-promise value {"a": 1} synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  object
 Received has value: <r>{"a": 1}</>
@@ -159,7 +159,7 @@ Received has value: <r>{"a": 1}</>
 exports[`.resolves fails non-promise value 4 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  number
 Received has value: <r>4</>
@@ -168,7 +168,7 @@ Received has value: <r>4</>
 exports[`.resolves fails non-promise value 4 synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  number
 Received has value: <r>4</>
@@ -177,7 +177,7 @@ Received has value: <r>4</>
 exports[`.resolves fails non-promise value null 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has value: <r>null</>
 `;
@@ -185,7 +185,7 @@ Received has value: <r>null</>
 exports[`.resolves fails non-promise value null synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has value: <r>null</>
 `;
@@ -193,7 +193,7 @@ Received has value: <r>null</>
 exports[`.resolves fails non-promise value true 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  boolean
 Received has value: <r>true</>
@@ -202,7 +202,7 @@ Received has value: <r>true</>
 exports[`.resolves fails non-promise value true synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has type:  boolean
 Received has value: <r>true</>
@@ -211,7 +211,7 @@ Received has value: <r>true</>
 exports[`.resolves fails non-promise value undefined 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has value: <r>undefined</>
 `;
@@ -219,7 +219,7 @@ Received has value: <r>undefined</>
 exports[`.resolves fails non-promise value undefined synchronously 1`] = `
 <d>expect(</><r>received</><d>).</>resolves<d>.</>not<d>.</>toBeDefined<d>()</>
 
-<b>Matcher error</>: <r>received</> value must be a promise
+<b>Matcher error</>: <r>received</> value must be a promise or a function returning a promise
 
 Received has value: <r>undefined</>
 `;
diff --git a/packages/expect/src/__tests__/matchers.test.js b/packages/expect/src/__tests__/matchers.test.js
index b3024bb134ab..db1956948a21 100644
--- a/packages/expect/src/__tests__/matchers.test.js
+++ b/packages/expect/src/__tests__/matchers.test.js
@@ -143,6 +143,13 @@ describe('.resolves', () => {
     ).resolves.toThrow();
   });
 
+  it('should resolve async function to toBe', async () => {
+    async function fn() {
+      return 'Test';
+    }
+    await jestExpect(fn).resolves.toBe('Test');
+  });
+
   for (const value of ['a', [1], () => {}, {a: 1}]) {
     it(`fails non-promise value ${stringify(value)} synchronously`, () => {
       let error;