The short answer
Import the test functions from vitest in each test file, and make sure jsconfig.json includes the tests:
import { describe, expect, it } from "vitest";
import { formatPrice } from "./price.js";
describe("formatPrice", () => {
it("formats cents as dollars", () => {
expect(formatPrice(1999)).toBe("$19.99");
});
it("uses the currency it is given", () => {
expect(formatPrice(500, "EUR")).toBe("€5.00");
});
});import { describe, expect, it } from "vitest";
import { formatPrice } from "./price.js";
describe("formatPrice", () => {
it("formats cents as dollars", () => {
expect(formatPrice(1999)).toBe("$19.99");
});
it("uses the currency it is given", () => {
expect(formatPrice(500, "EUR")).toBe("€5.00");
});
});jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": ["node"]
},
"include": ["*.js"]
} describe, it and expect come with their types from the vitest package, and the calls into price.js are checked like any other code: a wrong argument in a test is an error before the test runs.
Why it works
Vitest ships its own type declarations, so an import is enough for tsc to find them; nothing extra goes in types. The test file is a normal module in the program, so everything it calls is checked against the JSDoc of the code under test. That is often where checking pays off first: tests call your functions in many ways.
If you prefer the global style (globals: true in the Vitest config), tell tsc about the globals by listing vitest/globals in types:
import { formatPrice } from "./price.js";
describe("formatPrice", () => {
it("formats cents as dollars", () => {
expect(formatPrice(1999)).toBe("$19.99");
});
});jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": ["vitest/globals"]
},
"include": ["*.js"]
} The same thing in TypeScript
The TypeScript equivalent tab of the first example is the same test as a .ts file. It imports ./price.js, the JavaScript module, which a TypeScript project can do with allowJs.
What goes wrong
Globals used without their types are unknown names. With a types list in the config, describe and it are TS2593, whose message suggests installing a test runner’s types, and expect is TS2304. The type error in the call to formatPrice is reported as well:
import { formatPrice } from "./price.js";
describe("formatPrice", () => {error TS2593: Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha` and then add 'jest' or 'mocha' to the types field in your tsconfig.
it("formats cents as dollars", () => {error TS2593: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha` and then add 'jest' or 'mocha' to the types field in your tsconfig.
expect(formatPrice("19.99")).toBe("$19.99");error TS2304: Cannot find name 'expect'.error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
});
});jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Under TypeScript 6.0 and 7.0
Every example on this page also runs under TypeScript 6.0.3 and 7.0.2 when this site is built. One of them reports something different from 5.9.3, the version the page describes; the ✗ on its badge links to its entry below.
Conflicts between packages' types are reported under skipLibCheck
The pattern: A test file that imports from vitest in a project that also has @types/chai installed (vitest 3.2.7 and @types/chai 5.2.3 both declare containSubset).
Why: The port's list of changes says conflicting declarations now error in every file that contributes to them, so a conflict that skipLibCheck used to hide is reported (TS2300, "Duplicate identifier") in the two packages' type files. Nothing in the example's own files changes. Source: CHANGES.md: Error reporting under --skipLibCheck.
A test file that imports its test functions from vitest price.test.js
- TypeScript 5.9.3
No errors.
- TypeScript 6.0.3same as 5.9.3
No errors.
- TypeScript 7.0.2differs from 5.9.3
/project/node_modules/@types/chai/index.d.ts(539,13): error TS2300: Duplicate identifier 'containSubset'. /project/node_modules/vitest/dist/chunks/global.d.MAmajcmJ.d.ts(38,4): error TS2300: Duplicate identifier 'containSubset'.
What works in every version: Keep the packages on versions whose declarations agree. In our run, the same packages used through "types": ["vitest/globals"] reported nothing under 7.0.2.
The example Global test functions, declared by listing vitest/globals in types on this page shows it, with the same result under every version.
Gotchas
- TS2582 or TS2593. Without a
typeslist the same missing globals are TS2582; with one, TS2593. Both mean “these names come from a test runner’s types”. - Include the test files. A
jsconfig.jsonwhoseincludelists onlysrc/**/*.jsleavestest/unchecked; add the test folder, or use a pattern that covers it. - Matchers are loosely typed.
expect(value).toBe(other)accepts anyother, so assertions do not catch type mismatches the way calls to your code do.
Questions
What about Jest?
Per the Jest documentation, either import the functions from @jest/globals (import { describe, expect, it } from "@jest/globals") or install @types/jest and add "jest" to types. This page's examples run Vitest only.
Should test files be in the same jsconfig.json as the code?
Usually yes: the tests then check calls into your code with the same settings. If tests need different globals, a second config that extends the first and adds the test types keeps the main config clean.
Sources
- Getting Started · Vitest docs
- Configuring Vitest (globals) · Vitest docs
- Getting Started (type definitions) · Jest docs
Last reviewed 21 October 2026.