vitest: Property 'mockClear' does not exist on type 'Mocked<...>'.

Describe the bug

I’m trying to use the Mocked utility type from the vitest package, but when I try to invoke .mockClear() on the result, TypeScript claims that the property does not exist. In contrast, the Mock utility type does not have the same issue.

Reproduction

See https://www.typescriptlang.org/play?ssl=9&ssc=17&pln=1&pc=1#code/JYWwDg9gTgLgBAbzgWQgYwNYFMAmAaFdDAgN2DgF84AzKCEOAIjJiwGcZGBuAKB7QgA7DnBABGAFyFMAHgDaAXVIRgOAHxwAvHDIA6aoIAUBgJS9xukEQDCAGywBDKIbNwA9G7iCIcYGzYArlh8AsLwIABMUqiYuDIwAJ5gWBDUNIIa2noGxoJmPJGWNvZOLlzungAKdMmwCXAA5FaYdo5QDXA4EOxeEPBYAB5+8EJwicmNMdg4Mi5aGiQq6g26fNQBgmgwwKM5JogUQA

import { Mocked, Mock, vi } from "vitest";

const m1: Mock<[], void> = vi.fn(fn);
m1.mockClear(); // no issue

const m2: Mocked<typeof fn> = vi.fn(fn);
m2.mockClear(); // Property 'mockClear' does not exist on type 'Mocked<() => void>'.

function fn() {}

System Info

System:
    OS: macOS 14.1.2
    CPU: (10) arm64 Apple M1 Pro
    Memory: 156.53 MB / 32.00 GB
    Shell: 5.9 - /opt/homebrew/bin/zsh
  Binaries:
    Node: 21.4.0 - /opt/homebrew/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 10.2.4 - /opt/homebrew/bin/npm
    pnpm: 8.10.3 - /opt/homebrew/bin/pnpm
  Browsers:
    Chrome: 120.0.6099.71
    Edge: 120.0.2210.61
    Safari: 17.1.2
  npmPackages:
    @vitejs/plugin-react: ^4.2.1 => 4.2.1
    vitest: ^1.0.4 => 1.0.4

Used Package Manager

npm

Validations

About this issue

  • Original URL
  • State: open
  • Created 7 months ago
  • Comments: 18 (10 by maintainers)

Most upvoted comments

By the way, notice that all the changes I was making had extensive type tests. Type inference is powerful, but keep in mind that TypeScript is constantly improving it. This means that not all users might be able to use the typings. The lowest supported TypeScript version must be set. And here is the problem: how to test that?

The short answer: tstyche --target 4.8,5.0,latest.

With all the changes I made in Jest repo, I have contributed ca. 1000 type test assertions. As usually: solving a problem brings you to another problem. In this case, type testing at scale was rather an issue. I was hitting lots of limitations, one of them was running tests on specified TypeScript version. To address all that, I have build TSTyche, a type testing tool for TypeScript. Documentation is here: https://tstyche.org

Of course, I did consider other type testing libraries around. expect-type was one of them. Its expect style API is great and expect-type is a nice tool for small projects. But it does not scale.

I was already mentioning TSTyche in other issues. Now you can see it from another perspective.

Interesting. I did not realize they were different, we will need to align them then.

FWIW the typings shipped with @types/jest and imported from @jest/globals are different things. The migration guide demonstrates migration from @types/jest only.

The mock related typings from @jest/globals take only one argument (type of the function) and infer all what is needed. Here are the usage details of the Jest built-in types: https://jestjs.io/docs/mock-function-api#typescript-usage

@ezzatron is pointing not to @types/jest, but to @jest/globals. And it seems like the suggestion is to consider reshaping the mock types in similar fashion.

Thanks for explaining the motivation and comparison with jest.Mock. I think overall this issue could be considered as a feature request than jest-compatibility bug?

I tested a few cases below and it looks like there are some cases typescript can still magically infer vi.fn<TArgs, TReturn>. Could these be convenient enough? (These usages are not documented, so probably it’s not guaranteed to work forever though…)

Also in my opinion, if you’re mocking, then it’s likely that you want to lie about type-safety at some point, so I feel there’s nothing wrong with using something like vi.fn() as any as a quick escape-hatch.

import { vi, MockedFunction, beforeEach } from 'vitest'

// these would be in a library
type SomeFnType = (a: string) => void;
type SomeFnWithReturn = (a: string) => number;

// in test file
let someMockedFn: MockedFunction<SomeFnType>;
let someMockedFnWithReturn: MockedFunction<SomeFnWithReturn>;

beforeEach(() => {
  // @ts-expect-error
  someMockedFn = vi.fn(() => {});

  // ok (typescript can magically infer generics)
  someMockedFn = vi.fn();

  // ok (need to provide an argument then typscript can infer)
  someMockedFn = vi.fn((_a) => {});


  // @ts-expect-error
  someMockedFnWithReturn = vi.fn(() => {});

  // ok (typescript magic again though return type is lying)
  someMockedFnWithReturn = vi.fn();

  // ok (need to provide an argument and return value to match type)
  someMockedFnWithReturn = vi.fn((_a) => 0);
});

if you look at the definition of Mocked, it’s testing to see if the type argument is a function, and inferring the arg types and return types:

It’s using mapped type, so I think it’s only testing each property T[P] is function instead of T itself.

I’m trying to use the Mocked utility type from the vitest package

Can you explain your higher level goal with the use of Vitest’s typing utility? For example, why Mock type is not enough (not convenient) for your use case.

It’s hard to suggest without more contexts, but perhaps MockedFunction could be what you’re looking for?

import { Mocked, Mock, MockedFunction, vi } from "vitest";

const m3: MockedFunction<typeof fn> = vi.fn(fn);
m3.mockClear();

function fn() { }

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBAbzgWQgYwNYFMAmAaFdDA1TXAMQFcA7NGYCaggN2DgF84AzKCEOAESsYWAM4wBAbgBQ0tI3FwQARgBchTAB4A2gF0WEYDgB8cALxxWAOi7UAFLYCUMlVZBEAwgBssAQyh2znAA9MFw1BBwwKKilFiy8tSKIABM6qTYOJowAJ5gWBBc3NSmFta2DtTO0qFKKW6ePv6BkiFhAAq8+bA5cADk7pjeflB9cDgQYuEQ8FgAHtHwjHC5+f0ZuJqB5qbMhiZ9VgkK8CAAzOlEFDR0DNTZeQVFtqWWwDb2Ti5nDUNNAdVpFwbvRlhVHIgOEA