Skip to content

Commit

Permalink
fix: print value shape when .resolves and .rejects fails (#4137)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Sep 18, 2023
1 parent a084cea commit e649d78
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 28 deletions.
2 changes: 1 addition & 1 deletion examples/mocks/test/factory.test.ts
Expand Up @@ -75,7 +75,7 @@ describe('mocking with factory', () => {
expect((example as any).then).toBe('a then export')
expect((example as any).mocked).toBe(true)
expect(example.square(2, 3)).toBe(5)
expect(example.asyncSquare(2, 3)).resolves.toBe(5)
await expect(example.asyncSquare(2, 3)).resolves.toBe(5)
})

test('successfully with actual', () => {
Expand Down
49 changes: 25 additions & 24 deletions packages/expect/src/jest-expect.ts
@@ -1,4 +1,3 @@
import { AssertionError } from 'chai'
import { assertTypes, getColors } from '@vitest/utils'
import type { Constructable } from '@vitest/utils'
import type { EnhancedSpy } from '@vitest/spy'
Expand All @@ -13,6 +12,7 @@ import { recordAsyncExpect, wrapSoft } from './utils'

// Jest Expect Compact
export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
const { AssertionError } = chai
const c = () => getColors()

function def(name: keyof Assertion | (keyof Assertion)[], fn: ((this: Chai.AssertionStatic & Assertion, ...args: any[]) => any)) {
Expand Down Expand Up @@ -436,19 +436,16 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
if (called && isNot)
msg = formatCalls(spy, msg)

if ((called && isNot) || (!called && !isNot)) {
const err = new Error(msg)
err.name = 'AssertionError'
throw err
}
if ((called && isNot) || (!called && !isNot))
throw new AssertionError(msg)
})
def(['toHaveBeenCalledWith', 'toBeCalledWith'], function (...args) {
const spy = getSpy(this)
const spyName = spy.getMockName()
const pass = spy.mock.calls.some(callArg => jestEquals(callArg, args, [iterableEquality]))
const isNot = utils.flag(this, 'negate') as boolean

let msg = utils.getMessage(
const msg = utils.getMessage(
this,
[
pass,
Expand All @@ -458,12 +455,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
],
)

if ((pass && isNot) || (!pass && !isNot)) {
msg = formatCalls(spy, msg, args)
const err = new Error(msg)
err.name = 'AssertionError'
throw err
}
if ((pass && isNot) || (!pass && !isNot))
throw new AssertionError(formatCalls(spy, msg, args))
})
def(['toHaveBeenNthCalledWith', 'nthCalledWith'], function (times: number, ...args: any[]) {
const spy = getSpy(this)
Expand Down Expand Up @@ -595,7 +588,7 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
const pass = spy.mock.results.some(({ type, value: result }) => type === 'return' && jestEquals(value, result))
const isNot = utils.flag(this, 'negate') as boolean

let msg = utils.getMessage(
const msg = utils.getMessage(
this,
[
pass,
Expand All @@ -605,12 +598,8 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
],
)

if ((pass && isNot) || (!pass && !isNot)) {
msg = formatReturns(spy, msg, value)
const err = new Error(msg)
err.name = 'AssertionError'
throw err
}
if ((pass && isNot) || (!pass && !isNot))
throw new AssertionError(formatReturns(spy, msg, value))
})
def(['toHaveLastReturnedWith', 'lastReturnedWith'], function (value: any) {
const spy = getSpy(this)
Expand Down Expand Up @@ -650,8 +639,9 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
})

utils.addProperty(chai.Assertion.prototype, 'resolves', function __VITEST_RESOLVES__(this: any) {
const error = new Error('resolves')
utils.flag(this, 'promise', 'resolves')
utils.flag(this, 'error', new Error('resolves'))
utils.flag(this, 'error', error)
const test: Test = utils.flag(this, 'vitest-test')
const obj = utils.flag(this, 'object')

Expand All @@ -672,7 +662,12 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
return result.call(this, ...args)
},
(err: any) => {
throw new Error(`promise rejected "${String(err)}" instead of resolving`)
const _error = new AssertionError(
`promise rejected "${utils.inspect(err)}" instead of resolving`,
{ showDiff: false },
)
_error.stack = (error.stack as string).replace(error.message, _error.message)
throw _error
},
)

Expand All @@ -685,8 +680,9 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
})

utils.addProperty(chai.Assertion.prototype, 'rejects', function __VITEST_REJECTS__(this: any) {
const error = new Error('rejects')
utils.flag(this, 'promise', 'rejects')
utils.flag(this, 'error', new Error('rejects'))
utils.flag(this, 'error', error)
const test: Test = utils.flag(this, 'vitest-test')
const obj = utils.flag(this, 'object')
const wrapper = typeof obj === 'function' ? obj() : obj // for jest compat
Expand All @@ -704,7 +700,12 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
return async (...args: any[]) => {
const promise = wrapper.then(
(value: any) => {
throw new Error(`promise resolved "${String(value)}" instead of rejecting`)
const _error = new AssertionError(
`promise resolved "${utils.inspect(value)}" instead of rejecting`,
{ showDiff: false },
)
_error.stack = (error.stack as string).replace(error.message, _error.message)
throw _error
},
(err: any) => {
utils.flag(this, 'object', err)
Expand Down
43 changes: 40 additions & 3 deletions test/core/test/jest-expect.test.ts
@@ -1,5 +1,7 @@
/* eslint-disable no-sparse-arrays */
import { AssertionError } from 'node:assert'
import { fileURLToPath } from 'node:url'
import { resolve } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import { generateToBeMessage, setupColors } from '@vitest/expect'
import { processError } from '@vitest/utils/error'
Expand Down Expand Up @@ -604,6 +606,7 @@ describe('async expect', () => {

try {
expect(1).resolves.toEqual(2)
expect.unreachable()
}
catch (error) {
expect(error).toEqual(expectedError)
Expand Down Expand Up @@ -658,13 +661,15 @@ describe('async expect', () => {

try {
expect(1).rejects.toEqual(2)
expect.unreachable()
}
catch (error) {
expect(error).toEqual(expectedError)
}

try {
expect(() => 1).rejects.toEqual(2)
expect.unreachable()
}
catch (error) {
expect(error).toEqual(expectedError)
Expand All @@ -686,6 +691,7 @@ describe('async expect', () => {
const toStrictEqualError1 = generatedToBeMessage('toStrictEqual', '{ key: \'value\' }', '{ key: \'value\' }')
try {
expect(actual).toBe({ ...actual })
expect.unreachable()
}
catch (error: any) {
expect(error.message).toBe(toStrictEqualError1.message)
Expand All @@ -694,6 +700,7 @@ describe('async expect', () => {
const toStrictEqualError2 = generatedToBeMessage('toStrictEqual', 'FakeClass{}', 'FakeClass{}')
try {
expect(new FakeClass()).toBe(new FakeClass())
expect.unreachable()
}
catch (error: any) {
expect(error.message).toBe(toStrictEqualError2.message)
Expand All @@ -702,15 +709,16 @@ describe('async expect', () => {
const toEqualError1 = generatedToBeMessage('toEqual', '{}', 'FakeClass{}')
try {
expect({}).toBe(new FakeClass())
expect.unreachable()
}
catch (error: any) {
expect(error.message).toBe(toEqualError1.message)
// expect(error).toEqual('1234')
}

const toEqualError2 = generatedToBeMessage('toEqual', 'FakeClass{}', '{}')
try {
expect(new FakeClass()).toBe({})
expect.unreachable()
}
catch (error: any) {
expect(error.message).toBe(toEqualError2.message)
Expand Down Expand Up @@ -742,22 +750,51 @@ describe('async expect', () => {
})
})

it('printing error message', async () => {
const root = resolve(fileURLToPath(import.meta.url), '../../../../')
// have "\" on windows, and "/" on unix
const filename = fileURLToPath(import.meta.url).replace(root, '<root>')
try {
await expect(Promise.resolve({ foo: { bar: 42 } })).rejects.toThrow()
expect.unreachable()
}
catch (err: any) {
const stack = err.stack.replace(new RegExp(root, 'g'), '<root>')
expect(err.message).toMatchInlineSnapshot('"promise resolved \\"{ foo: { bar: 42 } }\\" instead of rejecting"')
expect(stack).toContain(`at ${filename}`)
}

try {
const error = new Error('some error')
Object.assign(error, { foo: { bar: 42 } })
await expect(Promise.reject(error)).resolves.toBe(1)
expect.unreachable()
}
catch (err: any) {
const stack = err.stack.replace(new RegExp(root, 'g'), '<root>')
expect(err.message).toMatchInlineSnapshot('"promise rejected \\"Error: some error { foo: { bar: 42 } }\\" instead of resolving"')
expect(stack).toContain(`at ${filename}`)
}
})

it('handle thenable objects', async () => {
await expect({ then: (resolve: any) => resolve(0) }).resolves.toBe(0)
await expect({ then: (_: any, reject: any) => reject(0) }).rejects.toBe(0)

try {
await expect({ then: (resolve: any) => resolve(0) }).rejects.toBe(0)
expect.unreachable()
}
catch (error) {
expect(error).toEqual(new Error('promise resolved "0" instead of rejecting'))
expect(error).toEqual(new Error('promise resolved "+0" instead of rejecting'))
}

try {
await expect({ then: (_: any, reject: any) => reject(0) }).resolves.toBe(0)
expect.unreachable()
}
catch (error) {
expect(error).toEqual(new Error('promise rejected "0" instead of resolving'))
expect(error).toEqual(new Error('promise rejected "+0" instead of resolving'))
}
})
})
Expand Down

0 comments on commit e649d78

Please sign in to comment.