[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

fix: allow catch/finally for async assertion (#9827)

authored by

Hiroshi Ogawa and committed by
GitHub
(Mar 9, 2026, 10:33 AM +0100) 031f02a8 3e9e096a

+135 -1
+2
packages/expect/src/utils.ts
··· 71 71 return promise.then(onFulfilled, onRejected) 72 72 }, 73 73 catch(onRejected) { 74 + resolved = true 74 75 return promise.catch(onRejected) 75 76 }, 76 77 finally(onFinally) { 78 + resolved = true 77 79 return promise.finally(onFinally) 78 80 }, 79 81 [Symbol.toStringTag]: 'Promise',
+4 -1
test/browser/specs/runner.test.ts
··· 209 209 }) 210 210 211 211 test(`stack trace points to correct file in every browser when failed`, async () => { 212 - expect.assertions(29) 212 + expect.assertions(30) 213 213 const { stderr } = await runBrowserTests({ 214 214 root: './fixtures/failing', 215 215 reporters: [ ··· 273 273 274 274 // index() is called from a bundled file 275 275 expect(stderr).toMatch(/failing.test.ts:39:(2|8)/) 276 + 277 + // "not awaited but with then/catch/finally" test should not produce warnings 278 + expect(stderr).not.toMatch(/failing.test.ts:4[3-8]/) 276 279 }) 277 280 278 281 test('user-event', async () => {
+113
test/cli/test/fails.test.ts
··· 117 117 `) 118 118 }) 119 119 120 + it('no async tracking after then/catch/finally', async () => { 121 + // resolve + then/catch/finally 122 + // rejects + then/catch/finally 123 + // expect.poll + then/catch/finally 124 + const { stderr, errorTree } = await runInlineTests({ 125 + 'await.test.js': ts` 126 + import { expect, test } from 'vitest'; 127 + 128 + test('resolves + then', async () => { 129 + await expect(Promise.resolve(1)).resolves.toBe(1).then() 130 + }) 131 + 132 + test('resolves + catch', async () => { 133 + await expect(Promise.resolve(1)).resolves.toBe(1).catch() 134 + }) 135 + 136 + test('resolves + finally', async () => { 137 + await expect(Promise.resolve(1)).resolves.toBe(1).finally() 138 + }) 139 + 140 + test('rejects + then', async () => { 141 + await expect(Promise.reject(1)).rejects.toBe(1).then() 142 + }) 143 + 144 + test('rejects + catch', async () => { 145 + await expect(Promise.reject(1)).rejects.toBe(1).catch() 146 + }) 147 + 148 + test('rejects + finally', async () => { 149 + await expect(Promise.reject(1)).rejects.toBe(1).finally() 150 + }) 151 + 152 + test('expect.poll + then', async () => { 153 + await expect.poll(() => 2).toBe(2).then() 154 + }) 155 + 156 + test('expect.poll + catch', async () => { 157 + await expect.poll(() => 2).toBe(2).catch() 158 + }) 159 + 160 + test('expect.poll + finally', async () => { 161 + await expect.poll(() => 2).toBe(2).finally() 162 + }) 163 + `, 164 + 'hang.test.js': ts` 165 + import { expect, test } from 'vitest'; 166 + 167 + test('resolves + then', async () => { 168 + expect(Promise.resolve(1)).resolves.toBe(1).then() 169 + }) 170 + 171 + test('resolves + catch', async () => { 172 + expect(Promise.resolve(1)).resolves.toBe(1).catch() 173 + }) 174 + 175 + test('resolves + finally', async () => { 176 + expect(Promise.resolve(1)).resolves.toBe(1).finally() 177 + }) 178 + 179 + test('rejects + then', async () => { 180 + expect(Promise.reject(1)).rejects.toBe(1).then() 181 + }) 182 + 183 + test('rejects + catch', async () => { 184 + expect(Promise.reject(1)).rejects.toBe(1).catch() 185 + }) 186 + 187 + test('rejects + finally', async () => { 188 + expect(Promise.reject(1)).rejects.toBe(1).finally() 189 + }) 190 + 191 + test('expect.poll + then', async () => { 192 + expect.poll(() => 2).toBe(2).then() 193 + }) 194 + 195 + test('expect.poll + catch', async () => { 196 + expect.poll(() => 2).toBe(2).catch() 197 + }) 198 + 199 + test('expect.poll + finally', async () => { 200 + expect.poll(() => 2).toBe(2).finally() 201 + }) 202 + `, 203 + }) 204 + expect(stderr).toMatchInlineSnapshot(`""`) 205 + expect(errorTree()).toMatchInlineSnapshot(` 206 + { 207 + "await.test.js": { 208 + "expect.poll + catch": "passed", 209 + "expect.poll + finally": "passed", 210 + "expect.poll + then": "passed", 211 + "rejects + catch": "passed", 212 + "rejects + finally": "passed", 213 + "rejects + then": "passed", 214 + "resolves + catch": "passed", 215 + "resolves + finally": "passed", 216 + "resolves + then": "passed", 217 + }, 218 + "hang.test.js": { 219 + "expect.poll + catch": "passed", 220 + "expect.poll + finally": "passed", 221 + "expect.poll + then": "passed", 222 + "rejects + catch": "passed", 223 + "rejects + finally": "passed", 224 + "rejects + then": "passed", 225 + "resolves + catch": "passed", 226 + "resolves + finally": "passed", 227 + "resolves + then": "passed", 228 + }, 229 + } 230 + `) 231 + }) 232 + 120 233 it('prints a warning if the assertion is not awaited in the browser mode', async () => { 121 234 const { stderr } = await runInlineTests({ 122 235 'base.test.js': ts`
+2
packages/browser/src/client/utils.ts
··· 54 54 return (promiseResult ||= promise(sourceError)).then(onFulfilled, onRejected) 55 55 }, 56 56 catch(onRejected) { 57 + awaited = true 57 58 return (promiseResult ||= promise(sourceError)).catch(onRejected) 58 59 }, 59 60 finally(onFinally) { 61 + awaited = true 60 62 return (promiseResult ||= promise(sourceError)).finally(onFinally) 61 63 }, 62 64 [Symbol.toStringTag]: 'Promise',
+12
test/browser/fixtures/failing/failing.test.ts
··· 38 38 it('correctly prints error from a bundled file', () => { 39 39 index() 40 40 }) 41 + 42 + it('not awaited but with then/catch/finally', async () => { 43 + await page.getByRole('button').click().then() 44 + await page.getByRole('button').click().catch() 45 + await page.getByRole('button').click().finally() 46 + await userEvent.click(page.getByRole('button')).then() 47 + await userEvent.click(page.getByRole('button')).catch() 48 + await userEvent.click(page.getByRole('button')).finally() 49 + page.getByRole('button').click().then() 50 + page.getByRole('button').click().catch() 51 + page.getByRole('button').click().finally() 52 + })
+2
packages/vitest/src/integrations/chai/poll.ts
··· 152 152 return (resultPromise ||= promise()).then(onFulfilled, onRejected) 153 153 }, 154 154 catch(onRejected) { 155 + awaited = true 155 156 return (resultPromise ||= promise()).catch(onRejected) 156 157 }, 157 158 finally(onFinally) { 159 + awaited = true 158 160 return (resultPromise ||= promise()).finally(onFinally) 159 161 }, 160 162 [Symbol.toStringTag]: 'Promise',