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: automock getters and setters #1903

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions examples/mocks/test/automocking.spec.ts
@@ -1,8 +1,10 @@
import type * as exampleModule from '../src/example'
import log from '../src/log'
import { A } from '../src/moduleA'
import { methodSymbol, moduleWithSymbol } from '../src/moduleWithSymbol'

vi.mock('../src/log')
vi.mock('../src/moduleA')
vi.mock('../src/moduleWithSymbol')

test('all mocked are valid', async () => {
Expand Down Expand Up @@ -55,3 +57,7 @@ test('automock properly restores mock', async () => {
expect(moduleWithSymbol[methodSymbol]()).toBe('hello')
expect(moduleWithSymbol.warn()).toBe('hello')
})

test('automock has a getter', () => {
expect(A).toBe('A')
})
12 changes: 8 additions & 4 deletions packages/vitest/src/runtime/mocker.ts
Expand Up @@ -209,10 +209,14 @@ export class VitestMocker {
const isModule = containerType === 'Module' || !!container.__esModule
for (const { key: property, descriptor } of getAllMockableProperties(container)) {
// Modules define their exports as getters. We want to process those.
if (!isModule) {
// TODO: Mock getters/setters somehow?
if (descriptor.get || descriptor.set)
continue
if (!isModule && descriptor.get) {
try {
Object.defineProperty(newContainer, property, descriptor)
}
catch (error) {
// Ignore errors, just move on to the next prop.
}
continue
simon-abbott marked this conversation as resolved.
Show resolved Hide resolved
}

// Skip special read-only props, we don't want to mess with those.
Expand Down
4 changes: 4 additions & 0 deletions test/core/src/mockedC.ts
Expand Up @@ -11,6 +11,10 @@ export class MockedC {
return mockedA()
}

get getOnlyProp(): number {
return 42
}

get getSetProp(): number {
return 123
}
Expand Down
2 changes: 1 addition & 1 deletion test/core/test/mocked.test.js
@@ -1,4 +1,4 @@
// this file should not be converted to js
// this file should not be converted to ts
// so it won't be transformed by esbuild

import { assert, test, vi } from 'vitest'
Expand Down
56 changes: 56 additions & 0 deletions test/core/test/mocked.test.ts
Expand Up @@ -13,6 +13,25 @@ vitest.mock('virtual-module', () => ({ value: 'mock' }))
vitest.mock('../src/mockedC')
vitest.mock('../src/mockedD')

/**
* Get a property descriptor from an object.
*
* This is different from `Object.getOwnPropertyDescriptor` because it recurses
* into the prototype chain until it either finds a match or reaches the end.
*
* @param object The object that contains the property.
* @param property The property.
* @returns The property's descriptor, or undefined if no matching property was found.
*/
function getPropertyDescriptor(object: any, property: PropertyKey) {
for (let o = object; o; o = Object.getPrototypeOf(o)) {
const descriptor = Object.getOwnPropertyDescriptor(o, property)
if (descriptor)
return descriptor
}
return undefined
}

test('submodule is mocked to return "two" as 3', () => {
assert.equal(3, two)
})
Expand Down Expand Up @@ -58,6 +77,43 @@ describe('mocked classes', () => {
expect(MockedC.prototype.doSomething).toHaveBeenCalledOnce()
expect(MockedC.prototype.doSomething).not.toHaveReturnedWith('A')
})

test('should mock getters', () => {
const instance = new MockedC()

expect(instance).toHaveProperty('getOnlyProp')
const descriptor = getPropertyDescriptor(instance, 'getOnlyProp')
expect(descriptor?.get).toBeDefined()
expect(descriptor?.set).not.toBeDefined()

expect(instance.getOnlyProp).toBe(42)
// @ts-expect-error Assign to the read-only prop to ensure it errors.
expect(() => instance.getOnlyProp = 4).toThrow()

const getterSpy = vi.spyOn(instance, 'getOnlyProp', 'get').mockReturnValue(456)
expect(instance.getOnlyProp).toEqual(456)
expect(getterSpy).toHaveBeenCalledOnce()
})

test('should mock getters and setters', () => {
const instance = new MockedC()

expect(instance).toHaveProperty('getSetProp')
const descriptor = getPropertyDescriptor(instance, 'getSetProp')
expect(descriptor?.get).toBeDefined()
expect(descriptor?.set).toBeDefined()

expect(instance.getSetProp).toBe(123)
expect(() => instance.getSetProp = 4).not.toThrow()

const getterSpy = vi.spyOn(instance, 'getSetProp', 'get').mockReturnValue(789)
expect(instance.getSetProp).toEqual(789)
expect(getterSpy).toHaveBeenCalledOnce()

const setterSpy = vi.spyOn(instance, 'getSetProp', 'set')
instance.getSetProp = 159
expect(setterSpy).toHaveBeenCalledWith(159)
})
})

describe('default exported classes', () => {
Expand Down