vitest icon indicating copy to clipboard operation
vitest copied to clipboard

[RFC] Testing types with expectTypeOf and assertType

Open sheremet-va opened this issue 2 years ago • 8 comments

Clear and concise description of the problem

Currently users have to come up with their own solutions to test types. Fo example, Vue defines a set of small utils:

export function expectType<T>(value: T): void
export function expectError<T>(value: T): void
export function expectAssignable<T, T2 extends T = T>(value: T2): void

This is not helpful for people who are coming into the world of types and testing their types. We want to provide a better developer experience.

Suggested solution

Vitest could provide an API for testing TypeScript types, since Vitest can already run typescript files.

Vitest can provide API, similar to expect, inspired by expect-type package:

expectTypeOf({a: 1}).toEqual<{a: number}>()
expectTypeOf({a: 1}).not.toMatch({b: 1})

Vitest can also provide API, similar to what Vue is using:

assertType<Type>(obj)
// @ts-expect-error
assertType<Type>(obj)

That way Vitest can provide similar set of tools that it already provides for runtime tests (expect and assert APIs).

Vitest will not fail, if tests are running with vitest and there is a type error, because there is no type checking involved, when running regular tests.

Vitest will fail, if tests are running with vitest type and there is a type error. When running type command, Vitest will run tsc under the hood. There will be an option in config to change command:

{
  test: {
    type: {
      command?: string // tsc --noEmit by default
      // potentially other options?
    }
  }
}

It will be recommended to have two pipelines for testing your application (currently this practice already exists, but tests are running with tsc or similar tool directly).

sheremet-va avatar Sep 02 '22 09:09 sheremet-va

I love this API. I've often felt (and have seen similar sentiment) that writing TypeScript for libraries is an over-exhaustive onus on library maintainers, primarily due to the lack of polished tooling surrounding the verification of types.

This would be a massive reason to migrate to Vitest for those of us that are looking for an all-in-one API.

crutchcorn avatar Sep 07 '22 06:09 crutchcorn

This is a pretty solid idea (expect-types looks cool)

I wonder if you're going to consider a command like vitest type then it could be worth exploring the TS LSP API on the test files to do things like snapshots for the types?

import thing from "thingy"

expect(thing).toHaveHoverInfoSnapshot(`const thing: Thing`)

( Effectively replicating the twoslash hovers in a test as an example )

It will take as much time as a tsc run (which it sounds like you may be doing anyway) and then the particular LSP calls you need to use which should be fast.

orta avatar Sep 07 '22 07:09 orta

Awesome idea (expect-types looks cool as suggested by @orta)

Can we test generics types ? for exemple

expect-types(object).toBeTypeOf(UserInterface)

MaloLebrin avatar Sep 07 '22 08:09 MaloLebrin

Love the idea and having a built-in snapshot support (like the example from @orta) would indeed be amazing.

tsd also provides a printType() helper which can be handy.

HiDeoo avatar Sep 07 '22 11:09 HiDeoo

Very cool, I like it a lot. By the way, I made a plugin recently that implements both APIs in Vitest. https://github.com/skarab42/vite-plugin-vitest-typescript-assert

skarab42 avatar Sep 07 '22 14:09 skarab42

I want this for days, looks great!

Sepush avatar Sep 07 '22 14:09 Sepush

Very cool, I like it a lot. By the way, I made a plugin recently that implements both APIs in Vitest. skarab42/vite-plugin-vitest-typescript-assert

I see that your plugin and tsd are using patched version of typescript. I wonder if it's possible to avoid that? My concern is that it wouldn't be possible to run types with Volar, for example. First version of this proposal expected developers to use types.command option, so users can replace tsc with vue-tsc, for example.

But of course, we can provide some kind of Provider interface to extend for custom use in the future.

sheremet-va avatar Sep 07 '22 14:09 sheremet-va

I wonder if it's possible to avoid that?

Unfortunately as long as the TS team doesn't make some functions in the TypeChecker publicly available, we won't be able to do anything more than what tsc --noEmit already does (which might be enough for many users), e.g. strict equality comparison.

skarab42 avatar Sep 07 '22 16:09 skarab42

Why not just use tsc or vue-tsc with @ts-expect-error to type check

xiaoxiangmoe avatar Oct 07 '22 11:10 xiaoxiangmoe

The first iteration is released in Vitest 0.25.0. Feel free to make suggestions on improving its support.

sheremet-va avatar Nov 07 '22 16:11 sheremet-va

This is definitely my favourite way to test types: https://github.com/microsoft/DefinitelyTyped-tools/blob/master/packages/dtslint/README.md#write-tests As far as I know, it is the only way to test the inferred type of a function parameter. All the other techniques can only test return types (as far as I can see, but I have never delved too deep due to this limitation).

I use this extensively in my library tests: image

You can see here that I am testing the type inferred for the function parameters from the function's contextual type inferrence. I don't know of any other library that can do this. Here, I am essentially testing the IntelliSense (and therefore the developer experience). The syntax uses comments to identify nodes of the typescript AST that need to have their type confirmed. I just wish that they also supported an inline comment syntax too.

markwhitfeld avatar Nov 10 '22 12:11 markwhitfeld