Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(vitest): return detailed error when using toHaveReturnedWith #2163

Merged
merged 9 commits into from Oct 19, 2022
36 changes: 31 additions & 5 deletions packages/vitest/src/integrations/chai/jest-expect.ts
Expand Up @@ -394,6 +394,20 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
msg += c.gray(`\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`)
return msg
}
const formatReturns = (spy: EnhancedSpy, msg: string, actualReturn?: any) => {
msg += c.gray(`\n\nReceived: \n${spy.mock.results.map((callReturn, i) => {
let methodCall = c.bold(` ${ordinalOf(i + 1)} ${spy.getMockName()} call return:\n\n`)
if (actualReturn)
methodCall += unifiedDiff(stringify(callReturn.value), stringify(actualReturn), { showLegend: false })
else
methodCall += stringify(callReturn).split('\n').map(line => ` ${line}`).join('\n')

methodCall += '\n'
return methodCall
}).join('\n')}`)
msg += c.gray(`\n\nNumber of calls: ${c.bold(spy.mock.calls.length)}\n`)
return msg
}
def(['toHaveBeenCalledTimes', 'toBeCalledTimes'], function (number: number) {
const spy = getSpy(this)
const spyName = spy.getMockName()
Expand Down Expand Up @@ -589,12 +603,24 @@ export const JestChaiExpect: ChaiPlugin = (chai, utils) => {
const spy = getSpy(this)
const spyName = spy.getMockName()
const pass = spy.mock.results.some(({ type, value: result }) => type === 'return' && jestEquals(value, result))
this.assert(
pass,
`expected "${spyName}" to be successfully called with #{exp}`,
`expected "${spyName}" to not be successfully called with #{exp}`,
value,
const isNot = utils.flag(this, 'negate') as boolean

let msg = utils.getMessage(
this,
[
pass,
`expected "${spyName}" to return with: #{exp} at least once`,
`expected "${spyName}" to not return with: #{exp}`,
value,
],
)

if ((pass && isNot) || (!pass && !isNot)) {
msg = formatReturns(spy, msg, value)
const err = new Error(msg)
err.name = 'AssertionError'
throw err
}
})
def(['toHaveLastReturnedWith', 'lastReturnedWith'], function (value: any) {
const spy = getSpy(this)
Expand Down
125 changes: 125 additions & 0 deletions test/core/test/mocked.test.ts
@@ -1,3 +1,4 @@
import type { AssertionError } from 'assert'
import { assert, describe, expect, test, vi, vitest } from 'vitest'
// @ts-expect-error not typed module
import { value as virtualValue } from 'virtual-module'
Expand Down Expand Up @@ -134,6 +135,130 @@ test('async functions should be mocked', () => {
expect(asyncFunc()).resolves.toBe('foo')
})

describe('mocked function which fails on toReturnWith', () => {
test('zero call', () => {
const mock = vi.fn(() => 1)
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets' rewrite these tests with toThrow:

expect(() => expect(mock).toReturnWith(2)).toThrowErrorMatchingInlineSnapshot()

Two reasons:

  1. less code
  2. more reliable code (if first assertion won't fail, we will not make assertions inside a "catch", but the test will pass)

expect(mock).toReturnWith(2)
}
catch (e) {
const throwObj = e as AssertionError
expect(throwObj.message).toMatchInlineSnapshot(`
"expected \\"spy\\" to return with: 2 at least once

Received:


Number of calls: 0
"
`)
}
})

test('just one call', () => {
const mock = vi.fn(() => 1)
mock()
try {
expect(mock).toReturnWith(2)
}
catch (e) {
const throwObj = e as AssertionError
expect(throwObj.message).toMatchInlineSnapshot(`
"expected \\"spy\\" to return with: 2 at least once

Received:
 1st spy call return:

 2
1


Number of calls: 1
"
`)
}
})

test('multi calls', () => {
const mock = vi.fn(() => 1)
mock()
mock()
mock()
try {
expect(mock).toReturnWith(2)
}
catch (e) {
const throwObj = e as AssertionError
expect(throwObj.message).toMatchInlineSnapshot(`
"expected \\"spy\\" to return with: 2 at least once

Received:
 1st spy call return:

 2
1

 2nd spy call return:

 2
1

 3rd spy call return:

 2
1


Number of calls: 3
"
`)
}
})

test('oject type', () => {
const mock = vi.fn(() => { return { a: '1' } })
mock()
mock()
mock()

try {
expect(mock).toReturnWith({ a: '4' })
}
catch (e) {
const throwObj = e as AssertionError
expect(throwObj.message).toMatchInlineSnapshot(`
"expected \\"spy\\" to return with: { a: '4' } at least once

Received:
 1st spy call return:

 Object {
- \\"a\\": \\"4\\",
+ \\"a\\": \\"1\\",
}

 2nd spy call return:

 Object {
- \\"a\\": \\"4\\",
+ \\"a\\": \\"1\\",
}

 3rd spy call return:

 Object {
- \\"a\\": \\"4\\",
+ \\"a\\": \\"1\\",
}


Number of calls: 3
"
`)
}
})
})

// This is here because mocking streams previously caused some problems (#1671).
test('streams', () => {
expect(exportedStream).toBeDefined()
Expand Down