Skip to content

Mocked base classes cause overridden methods to also be mocked #8870

@matanui159

Description

@matanui159

Describe the bug

If a base class is mocked, any subclasses that extend from it will also have all matching methods mocked too. This means there's no way to test the overridden method of a subclass since it will be replaced with a mock of the base class. This is better explained with code below.

This bug seems to have started with v4 and is present in all v4 versions

Reproduction

First a base class and a subclass which overrides the one method, in separate files so the base class can be auto-mocked in isolation:

// foo.ts
export class Foo {
    doSomething(): boolean {
        return false;
    }
}

// bar.ts
import { Foo } from './foo.js';

export class Bar extends Foo {
    override doSomething(): boolean {
        return true;
    }

    doSomethingElse(): boolean {
        return true;
    }
}

Then a series of tests. All the doSomething tests fail despite not being explicitly mocked. While the doSomethingElse tests all pass since it does not match the name of a mocked method in the base class:

// bar.spec.ts
import { describe, expect, it, vi } from 'vitest';
import { Bar } from './bar.js';

vi.mock(import('./foo.js'));

describe('Bar', () => {
    describe('doSomething', () => {
        it('returns true', () => {
            const bar = new Bar();
            expect(bar.doSomething()).toBe(true);
        });

        it('should match the prototype', () => {
            const bar = new Bar();
            expect(bar.doSomething).toBe(Bar.prototype.doSomething);
        });

        it('should not be mocked', () => {
            const bar = new Bar();
            expect(bar.doSomething).not.toHaveProperty('mock')
        });
    });

    describe('doSomethingElse', () => {
        it('returns true', () => {
            const bar = new Bar();
            expect(bar.doSomethingElse()).toBe(true);
        });

        it('should match the prototype', () => {
            const bar = new Bar();
            expect(bar.doSomethingElse).toBe(Bar.prototype.doSomethingElse);
        });

        it('should not be mocked', () => {
            const bar = new Bar();
            expect(bar.doSomethingElse).not.toHaveProperty('mock')
        });
    });
});

System Info

System:
    OS: macOS 26.0.1
    CPU: (16) arm64 Apple M4 Max
    Memory: 1.06 GB / 64.00 GB
    Shell: 4.1.2 - /opt/homebrew/bin/fish
  Binaries:
    Node: 22.18.0 - /Users/matanui159/.local/state/fnm_multishells/12587_1761795789805/bin/node
    Yarn: 1.22.22 - /Users/matanui159/.local/state/fnm_multishells/12587_1761795789805/bin/yarn
    npm: 10.9.3 - /Users/matanui159/.local/state/fnm_multishells/12587_1761795789805/bin/npm
    pnpm: 10.20.0 - /Users/matanui159/.local/state/fnm_multishells/12587_1761795789805/bin/pnpm
  Browsers:
    Chrome: 141.0.7390.123
    Edge: 141.0.3537.99
    Safari: 26.0.1
  npmPackages:
    vitest: ^4.0.5 => 4.0.5

Used Package Manager

npm

Validations

Metadata

Metadata

Assignees

Labels

p3-minor-bugAn edge case that only affects very specific usage (priority)

Type

Projects

Status

Approved

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions