Skip to content

Commit f8e742d

Browse files
committed
feat: create and restore spies with other test frameworks
Refs #143 By default the mock will use sinon or jest support to create and restore spies. This commit adds a `spy` option that allows to use a different testing framework, by providing a method to create spies, and one to restore them. For example, with vitest: ```ts const router = createRouterMock({ spy: { create: fn => vi.fn(fn), restore: spy => () => spy.restore() } }); ```
1 parent f87b32c commit f8e742d

File tree

1 file changed

+74
-14
lines changed

1 file changed

+74
-14
lines changed

src/router.ts

+74-14
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
RouterOptions,
1212
START_LOCATION,
1313
} from 'vue-router'
14-
import type { SinonStatic } from 'sinon'
14+
import type { SinonSpy, SinonStatic } from 'sinon'
1515

1616
export const EmptyView = defineComponent({
1717
name: 'RouterMockEmptyView',
@@ -28,28 +28,48 @@ function getJestGlobal() {
2828
}
2929

3030
/**
31-
* Creates a spy on a function and allows clearing the mock.
31+
* Creates a spy on a function
3232
*
3333
* @param fn function to spy on
34-
* @returns [spy, mockClear()]
34+
* @returns spy
3535
*/
36-
function createSpy<Fn extends (...args: any[]) => any>(
37-
fn: Fn
38-
): [Fn, () => void] {
36+
function createSpyAuto<Fn extends (...args: any[]) => any>(fn: Fn): Fn {
3937
const sinon = getSinonGlobal()
4038
if (sinon) {
41-
const spy = sinon.spy(fn)
42-
return [spy as unknown as Fn, () => spy.resetHistory()]
39+
return sinon.spy(fn) as unknown as Fn
4340
}
4441

4542
const jest = getJestGlobal()
4643
if (jest) {
47-
const spy = jest.fn(fn)
48-
return [spy as unknown as Fn, () => spy.mockClear()]
44+
return jest.fn(fn) as unknown as Fn
4945
}
5046

5147
console.error(
52-
`Couldn't detect a global spy (tried jest and sinon). Make sure to provide a "createSpy" option when creating the router mock.`
48+
`Couldn't detect a global spy (tried jest and sinon). Make sure to provide a "spy.create" option when creating the router mock.`
49+
)
50+
throw new Error('No Spy Available')
51+
}
52+
53+
/**
54+
* Restores a mock
55+
*
56+
* @param spy the spy to restore
57+
*/
58+
function restoreSpyAuto<Fn extends (...args: any[]) => any>(
59+
spy: Fn
60+
): () => void {
61+
const sinon = getSinonGlobal()
62+
if (sinon) {
63+
return () => (spy as unknown as SinonSpy).resetHistory()
64+
}
65+
66+
const jest = getJestGlobal()
67+
if (jest) {
68+
return () => (spy as unknown as jest.Mock).mockClear()
69+
}
70+
71+
console.error(
72+
`Couldn't detect a global spy (tried jest and sinon). Make sure to provide a "spy.restore" option when creating the router mock.`
5373
)
5474
throw new Error('No Spy Available')
5575
}
@@ -112,6 +132,21 @@ export interface RouterMock extends Router {
112132
reset(): void
113133
}
114134

135+
/**
136+
* Options passed to the `spy` option of the `createRouterMock` function
137+
*/
138+
export interface RouterMockSpyOptions {
139+
/**
140+
* Creates a spy (for example, `create: fn => vi.fn(fn)` with vitest)
141+
*/
142+
create: (...args: any[]) => any
143+
144+
/**
145+
* function to restore a spy (for example, `restore: spy => () => spy.restore()` with vitest)
146+
*/
147+
restore: (spy: any) => () => void
148+
}
149+
115150
/**
116151
* TODO: Allow passing a custom spy and detect common global ones like jest and cypress.
117152
*/
@@ -155,6 +190,22 @@ export interface RouterMockOptions extends Partial<RouterOptions> {
155190
* disable that behavior and throw when `router.push()` fails.
156191
*/
157192
noUndeclaredRoutes?: boolean
193+
194+
/**
195+
* By default the mock will use sinon or jest support to create and restore spies.
196+
* This option allows to use a different testing framework,
197+
* by providing a method to create spies, and one to restore them.
198+
* For example, with vitest:
199+
* ```
200+
* const router = createRouterMock({
201+
* spy: {
202+
* create: fn => vi.fn(fn),
203+
* restore: spy => () => spy.restore()
204+
* }
205+
* });
206+
* ```
207+
*/
208+
spy?: RouterMockSpyOptions
158209
}
159210

160211
/**
@@ -188,7 +239,10 @@ export function createRouterMock(options: RouterMockOptions = {}): RouterMock {
188239

189240
const { push, addRoute, replace, beforeEach, beforeResolve } = router
190241

191-
const [addRouteMock, addRouteMockClear] = createSpy(
242+
const createSpy = options.spy?.create ?? createSpyAuto
243+
const restoreSpy = options.spy?.restore ?? restoreSpyAuto
244+
245+
const addRouteMock = createSpy(
192246
(
193247
parentRecordName: Required<RouteRecordRaw>['name'] | RouteRecordRaw,
194248
record?: RouteRecordRaw
@@ -206,14 +260,20 @@ export function createRouterMock(options: RouterMockOptions = {}): RouterMock {
206260
}
207261
)
208262

209-
const [pushMock, pushMockClear] = createSpy((to: RouteLocationRaw) => {
263+
const addRouteMockClear = restoreSpy(addRouteMock)
264+
265+
const pushMock = createSpy((to: RouteLocationRaw) => {
210266
return consumeNextReturn(to)
211267
})
212268

213-
const [replaceMock, replaceMockClear] = createSpy((to: RouteLocationRaw) => {
269+
const pushMockClear = restoreSpy(pushMock)
270+
271+
const replaceMock = createSpy((to: RouteLocationRaw) => {
214272
return consumeNextReturn(to, { replace: true })
215273
})
216274

275+
const replaceMockClear = restoreSpy(replaceMock)
276+
217277
router.push = pushMock
218278
router.replace = replaceMock
219279
router.addRoute = addRouteMock

0 commit comments

Comments
 (0)