[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(expect): fix sinon-chai style API (#9943)

authored by

Hiroshi Ogawa and committed by
GitHub
(Mar 23, 2026, 11:25 AM +0100) 0f08dda2 0d5f9d6e

+264 -19
+61
pnpm-lock.yaml
··· 108 108 playwright: 109 109 specifier: ^1.58.2 110 110 version: 1.58.2 111 + sinon: 112 + specifier: ^21.0.3 113 + version: 21.0.3 114 + sinon-chai: 115 + specifier: ^4.0.1 116 + version: 4.0.1 111 117 sirv: 112 118 specifier: ^3.0.2 113 119 version: 3.0.2 ··· 1512 1518 react-18: 1513 1519 specifier: npm:react@18.3.1 1514 1520 version: react@18.3.1 1521 + sinon: 1522 + specifier: 'catalog:' 1523 + version: 21.0.3 1524 + sinon-chai: 1525 + specifier: 'catalog:' 1526 + version: 4.0.1(chai@6.2.2)(sinon@21.0.3) 1515 1527 temporal-polyfill: 1516 1528 specifier: ~0.3.0 1517 1529 version: 0.3.0 ··· 4971 4983 '@sinonjs/fake-timers@15.0.0': 4972 4984 resolution: {integrity: sha512-dlUB2oL+hDIYkIq/OWFBDhQAuU6kDey3eeMiYpVb7UXHhkMq/r1HloKXAbJwJZpYWkFWsydLjMqDpueMUEOjXQ==} 4973 4985 4986 + '@sinonjs/fake-timers@15.1.1': 4987 + resolution: {integrity: sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==} 4988 + 4989 + '@sinonjs/samsam@9.0.3': 4990 + resolution: {integrity: sha512-ZgYY7Dc2RW+OUdnZ1DEHg00lhRt+9BjymPKHog4PRFzr1U3MbK57+djmscWyKxzO1qfunHqs4N45WWyKIFKpiQ==} 4991 + 4974 4992 '@standard-schema/spec@1.1.0': 4975 4993 resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} 4976 4994 ··· 6638 6656 diff-sequences@29.6.3: 6639 6657 resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} 6640 6658 engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 6659 + 6660 + diff@8.0.3: 6661 + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} 6662 + engines: {node: '>=0.3.1'} 6641 6663 6642 6664 dom-accessibility-api@0.5.16: 6643 6665 resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} ··· 9502 9524 simple-swizzle@0.2.2: 9503 9525 resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} 9504 9526 9527 + sinon-chai@4.0.1: 9528 + resolution: {integrity: sha512-xMKEEV3cYHC1G+boyr7QEqi80gHznYsxVdC9CdjP5JnCWz/jPGuXQzJz3PtBcb0CcHAxar15Y5sjLBoAs6a0yA==} 9529 + peerDependencies: 9530 + chai: ^5.0.0 || ^6.0.0 9531 + sinon: '>=4.0.0' 9532 + 9533 + sinon@21.0.3: 9534 + resolution: {integrity: sha512-0x8TQFr8EjADhSME01u1ZK31yv2+bd6Z5NrBCHVM+n4qL1wFqbxftmeyi3bwlr49FbbzRfrqSFOpyHCOh/YmYA==} 9535 + 9505 9536 sirv@3.0.2: 9506 9537 resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} 9507 9538 engines: {node: '>=18'} ··· 9950 9981 9951 9982 type-detect@4.0.8: 9952 9983 resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} 9984 + engines: {node: '>=4'} 9985 + 9986 + type-detect@4.1.0: 9987 + resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} 9953 9988 engines: {node: '>=4'} 9954 9989 9955 9990 type-fest@0.16.0: ··· 13416 13451 dependencies: 13417 13452 '@sinonjs/commons': 3.0.1 13418 13453 13454 + '@sinonjs/fake-timers@15.1.1': 13455 + dependencies: 13456 + '@sinonjs/commons': 3.0.1 13457 + 13458 + '@sinonjs/samsam@9.0.3': 13459 + dependencies: 13460 + '@sinonjs/commons': 3.0.1 13461 + type-detect: 4.1.0 13462 + 13419 13463 '@standard-schema/spec@1.1.0': {} 13420 13464 13421 13465 '@stylistic/eslint-plugin@5.9.0(eslint@10.0.3(jiti@2.6.1))': ··· 15275 15319 wrappy: 1.0.2 15276 15320 15277 15321 diff-sequences@29.6.3: {} 15322 + 15323 + diff@8.0.3: {} 15278 15324 15279 15325 dom-accessibility-api@0.5.16: {} 15280 15326 ··· 18839 18885 dependencies: 18840 18886 is-arrayish: 0.3.2 18841 18887 18888 + sinon-chai@4.0.1(chai@6.2.2)(sinon@21.0.3): 18889 + dependencies: 18890 + chai: 6.2.2 18891 + sinon: 21.0.3 18892 + 18893 + sinon@21.0.3: 18894 + dependencies: 18895 + '@sinonjs/commons': 3.0.1 18896 + '@sinonjs/fake-timers': 15.1.1 18897 + '@sinonjs/samsam': 9.0.3 18898 + diff: 8.0.3 18899 + supports-color: 7.2.0 18900 + 18842 18901 sirv@3.0.2: 18843 18902 dependencies: 18844 18903 '@polka/url': 1.0.0-next.24 ··· 19316 19375 prelude-ls: 1.2.1 19317 19376 19318 19377 type-detect@4.0.8: {} 19378 + 19379 + type-detect@4.1.0: {} 19319 19380 19320 19381 type-fest@0.16.0: {} 19321 19382
+2
pnpm-workspace.yaml
··· 75 75 obug: ^2.1.1 76 76 pathe: ^2.0.3 77 77 playwright: ^1.58.2 78 + sinon: ^21.0.3 79 + sinon-chai: ^4.0.1 78 80 sirv: ^3.0.2 79 81 std-env: ^4.0.0-rc.1 80 82 strip-literal: ^3.1.0
+4 -8
docs/api/expect.md
··· 1559 1559 1560 1560 ## returned <Version>4.1.0</Version> {#returned} 1561 1561 1562 - - **Type:** `Assertion` (property, not a method) 1562 + - **Type:** `(value: any) => void` 1563 1563 1564 - Chai-style assertion that checks if a spy returned successfully at least once. This is equivalent to `toHaveReturned()`. 1565 - 1566 - ::: tip 1567 - This is a property assertion following sinon-chai conventions. Access it without parentheses: `expect(spy).to.have.returned` 1568 - ::: 1564 + Chai-style assertion that checks if a spy returned a specific value at least once. This is equivalent to `toHaveReturnedWith(value)`. 1569 1565 1570 1566 ```ts 1571 1567 import { expect, test, vi } from 'vitest' 1572 1568 1573 1569 test('spy returned', () => { 1574 - const spy = vi.fn(() => 'result') 1570 + const spy = vi.fn(() => 'value') 1575 1571 1576 1572 spy() 1577 1573 1578 - expect(spy).to.have.returned 1574 + expect(spy).to.have.returned('value') 1579 1575 }) 1580 1576 ``` 1581 1577
+1 -2
docs/guide/migration.md
··· 742 742 | `spy.callCount(n)` | `callCount(n)` | Spy was called n times | 743 743 | `spy.calledWith(...)` | `calledWith(...)` | Spy was called with specific args | 744 744 | `spy.calledOnceWith(...)` | `calledOnceWith(...)` | Spy was called once with specific args | 745 - | `spy.returned` | `returned` | Spy returned successfully | 746 - | `spy.returnedWith(value)` | `returnedWith(value)` | Spy returned specific value | 745 + | `spy.returned(value)` | `returned` | Spy returned specific value | 747 746 748 747 See the [Chai-Style Spy Assertions](/api/expect#chai-style-spy-assertions) documentation for the complete list. 749 748
+2
test/core/package.json
··· 38 38 "obug": "^2.1.1", 39 39 "react": "^19.2.4", 40 40 "react-18": "npm:react@18.3.1", 41 + "sinon": "catalog:", 42 + "sinon-chai": "catalog:", 41 43 "temporal-polyfill": "~0.3.0", 42 44 "tinyrainbow": "catalog:", 43 45 "tinyspy": "^4.0.4",
+7 -3
packages/expect/src/chai-style-assertions.ts
··· 44 44 name: keyof Assertion, 45 45 delegateTo: keyof Assertion, 46 46 ) { 47 - utils.addChainableMethod( 47 + utils.addMethod( 48 48 chai.Assertion.prototype, 49 49 name, 50 50 function (this: Chai.AssertionStatic & Assertion, ...args: any[]) { ··· 56 56 } 57 57 return jestMethod.call(this, ...args) 58 58 }, 59 - () => {}, 60 59 ) 61 60 } 62 61 62 + // API to (somewhat) mirror sinon-chai 63 + // https://github.com/chaijs/sinon-chai 63 64 defProperty('called', 'toHaveBeenCalled') 64 65 defProperty('calledOnce', 'toHaveBeenCalledOnce') 65 - defProperty('returned', 'toHaveReturned') 66 66 defPropertyWithArgs('calledTwice', 'toHaveBeenCalledTimes', 2) 67 67 defPropertyWithArgs('calledThrice', 'toHaveBeenCalledTimes', 3) 68 68 ··· 71 71 defMethod('calledOnceWith', 'toHaveBeenCalledExactlyOnceWith') 72 72 defMethod('lastCalledWith', 'toHaveBeenLastCalledWith') 73 73 defMethod('nthCalledWith', 'toHaveBeenNthCalledWith') 74 + defMethod('returned', 'toHaveReturned') 74 75 defMethod('returnedWith', 'toHaveReturnedWith') 75 76 defMethod('returnedTimes', 'toHaveReturnedTimes') 76 77 defMethod('lastReturnedWith', 'toHaveLastReturnedWith') 77 78 defMethod('nthReturnedWith', 'toHaveNthReturnedWith') 78 79 defMethod('calledBefore', 'toHaveBeenCalledBefore') 79 80 defMethod('calledAfter', 'toHaveBeenCalledAfter') 81 + 82 + // TODO: implement 83 + // defMethod('thrown', 'toHaveThrown') 80 84 }
+4 -4
packages/expect/src/types.ts
··· 836 836 nthCalledWith: <E extends any[]>(n: number, ...args: E) => void 837 837 838 838 /** 839 - * Checks that a spy returned successfully at least once. 840 - * Chai-style equivalent of `toHaveReturned`. 839 + * Checks that a spy returned a specific value at least once. 840 + * Chai-style equivalent of `toHaveReturnedWith`. 841 841 * 842 842 * @example 843 - * expect(spy).to.have.returned 843 + * expect(spy).to.have.returned('value') 844 844 */ 845 - readonly returned: Assertion 845 + returned: <E>(value: E) => void 846 846 847 847 /** 848 848 * Checks that a spy returned a specific value at least once.
+181
test/core/test/chai-style-assertions-sinon.test.ts
··· 1 + /* eslint-disable ts/no-unused-expressions */ 2 + // @ts-expect-error no type override otherwise vitest ones broken 3 + import sinon from 'sinon' 4 + // @ts-expect-error same 5 + import sinonChai from 'sinon-chai' 6 + import { chai, describe, expect, it } from 'vitest' 7 + 8 + chai.use(sinonChai) 9 + 10 + describe('sinon-chai assertions', () => { 11 + describe('called', () => { 12 + it('passes when spy was called', () => { 13 + const spy = sinon.spy() 14 + spy() 15 + expect(spy).to.have.been.called 16 + }) 17 + 18 + it('negated: passes when spy was not called', () => { 19 + const spy = sinon.spy() 20 + expect(spy).to.not.have.been.called 21 + }) 22 + }) 23 + 24 + describe('callCount', () => { 25 + it('passes when spy was called exact number of times', () => { 26 + const spy = sinon.spy() 27 + spy() 28 + spy() 29 + spy() 30 + expect(spy).to.have.callCount(3) 31 + }) 32 + 33 + it('negated: passes when spy was called different number of times', () => { 34 + const spy = sinon.spy() 35 + spy() 36 + expect(spy).to.not.have.callCount(3) 37 + }) 38 + }) 39 + 40 + describe('calledWith', () => { 41 + it('passes when spy was called with specific arguments', () => { 42 + const spy = sinon.spy() 43 + spy('arg1', 'arg2') 44 + expect(spy).to.have.been.calledWith('arg1', 'arg2') 45 + }) 46 + 47 + it('passes when spy was called with arguments among other calls', () => { 48 + const spy = sinon.spy() 49 + spy('other') 50 + spy('arg1', 'arg2') 51 + spy('another') 52 + expect(spy).to.have.been.calledWith('arg1', 'arg2') 53 + }) 54 + 55 + it('negated: passes when spy was not called with specific arguments', () => { 56 + const spy = sinon.spy() 57 + spy('other', 'args') 58 + expect(spy).to.not.have.been.calledWith('arg1', 'arg2') 59 + }) 60 + }) 61 + 62 + describe('calledOnce', () => { 63 + it('passes when spy was called exactly once', () => { 64 + const spy = sinon.spy() 65 + spy() 66 + expect(spy).to.have.been.calledOnce 67 + }) 68 + 69 + it('negated: passes when spy was not called once', () => { 70 + const spy = sinon.spy() 71 + spy() 72 + spy() 73 + expect(spy).to.not.have.been.calledOnce 74 + }) 75 + }) 76 + 77 + describe('calledOnceWith', () => { 78 + it('passes when spy was called exactly once with specific arguments', () => { 79 + const spy = sinon.spy() 80 + spy('arg1', 'arg2') 81 + expect(spy).to.have.been.calledOnceWith('arg1', 'arg2') 82 + }) 83 + 84 + it('negated: passes when spy was not called once with specific arguments', () => { 85 + const spy = sinon.spy() 86 + spy('arg1', 'arg2') 87 + spy('arg1', 'arg2') 88 + expect(spy).to.not.have.been.calledOnceWith('arg1', 'arg2') 89 + }) 90 + }) 91 + 92 + describe('calledTwice', () => { 93 + it('passes when spy was called exactly twice', () => { 94 + const spy = sinon.spy() 95 + spy() 96 + spy() 97 + expect(spy).to.have.been.calledTwice 98 + }) 99 + 100 + it('negated: passes when spy was not called twice', () => { 101 + const spy = sinon.spy() 102 + spy() 103 + expect(spy).to.not.have.been.calledTwice 104 + }) 105 + }) 106 + 107 + describe('calledThrice', () => { 108 + it('passes when spy was called exactly three times', () => { 109 + const spy = sinon.spy() 110 + spy() 111 + spy() 112 + spy() 113 + expect(spy).to.have.been.calledThrice 114 + }) 115 + 116 + it('negated: passes when spy was not called three times', () => { 117 + const spy = sinon.spy() 118 + spy() 119 + spy() 120 + expect(spy).to.not.have.been.calledThrice 121 + }) 122 + }) 123 + 124 + describe('calledBefore / calledAfter', () => { 125 + it('passes when spy was called before another spy', () => { 126 + const spy1 = sinon.spy() 127 + const spy2 = sinon.spy() 128 + spy1() 129 + spy2() 130 + expect(spy1).to.have.been.calledBefore(spy2) 131 + }) 132 + 133 + it('passes when spy was called after another spy', () => { 134 + const spy1 = sinon.spy() 135 + const spy2 = sinon.spy() 136 + spy1() 137 + spy2() 138 + expect(spy2).to.have.been.calledAfter(spy1) 139 + }) 140 + }) 141 + 142 + describe('returned', () => { 143 + it('passes when spy returned specific value', () => { 144 + const spy = sinon.spy(() => 'value') 145 + spy() 146 + expect(spy).to.have.returned('value') 147 + }) 148 + 149 + it('passes with no arguments (checks for undefined)', () => { 150 + const spy = sinon.spy(() => {}) 151 + spy() 152 + expect(spy).to.have.returned(undefined) 153 + }) 154 + 155 + it('negated: passes when spy did not return specific value', () => { 156 + const spy = sinon.spy(() => 'other') 157 + spy() 158 + expect(spy).to.not.have.returned('value') 159 + }) 160 + }) 161 + 162 + // TODO: implement `thrown` 163 + // describe('thrown', () => { 164 + // it('passes when spy threw', () => { 165 + // const spy = sinon.spy(() => { 166 + // throw new Error('test error') 167 + // }) 168 + // try { 169 + // spy() 170 + // } 171 + // catch {} 172 + // expect(spy).to.have.thrown() 173 + // }) 174 + 175 + // it('negated: passes when spy did not throw', () => { 176 + // const spy = sinon.spy(() => 'value') 177 + // spy() 178 + // expect(spy).to.not.have.thrown() 179 + // }) 180 + // }) 181 + })
+2 -2
test/core/test/chai-style-assertions.test.ts
··· 206 206 it('passes when spy returned successfully', () => { 207 207 const spy = vi.fn(() => 'value') 208 208 spy() 209 - expect(spy).to.have.returned 209 + expect(spy).to.have.returned('value') 210 210 }) 211 211 212 212 it('fails when spy threw an error', () => { ··· 218 218 } 219 219 catch {} 220 220 expect(() => { 221 - expect(spy).to.have.returned 221 + expect(spy).to.have.returned('value') 222 222 }).toThrow(/expected "testSpy" to be successfully called at least once/) 223 223 }) 224 224