Gathering detailed insights and metrics for type-testing
Gathering detailed insights and metrics for type-testing
Gathering detailed insights and metrics for type-testing
Gathering detailed insights and metrics for type-testing
npm install type-testing
Module System
Unable to determine the module system for this package.
Min. Node Version
Typescript Support
Node Version
NPM Version
58 Stars
8 Commits
3 Forks
7 Watching
1 Branches
12 Contributors
Updated on 14 Nov 2024
TypeScript (100%)
Cumulative downloads
Total Downloads
Last day
81.9%
342
Compared to previous day
Last week
32.9%
1,591
Compared to previous week
Last month
0.1%
5,472
Compared to previous month
Last year
10,779.5%
75,395
Compared to previous year
4
🌱 A micro library for testing your TypeScript types.
Sometimes your project's TypeScript types really matter. More and more often, the TypeScript types themselves are a core part of the product. But there hasn't been a good way to make sure the types don't subtly break from day to day. This is the problem type-testing
solves.
A lot of popular projects with incredible type inferencing already use the code in type-testing
. For example Zod, TanStack Query, zustand, tRPC, MUI, type-fest, ts-reset, and the TypeScript Challenges all have variants of the same code. We collected that code here and wrote tests for them (yes: test inception).
Before testing your types | After using type-testing |
---|---|
:dizzy_face: Your inferred types work great on Monday, but by Friday you realize they don't infer correctly anymore. | :green_salad: At 11:53am on Wednesday, a type test fails and you realize you should probably stop to eat some lunch before you break anything else. |
:see_no_evil: You want to make awesome TypeScript types.. but every time you try to add or fix something, you are left wondering if you broke something else. | :mechanical_arm: You can refactor with confidence. |
:crossed_fingers: Your open source project can be tough to keep up with. You want to accept community PRs but you're not sure if they possibly break something. | :partying_face: A community PR won't break anything, and you can request new tests that cover any areas of concern. |
:clown_face: You wonder about what will happen if there's some weird TypeScript edge-case where never or unknown or any is passed to your type by an end-user. | :mage: You don't have to be a TypeScript wizard to write great TypeScript types. You can write a test and be sure things will work as expected! |
What if you have a fancy TypeScript function with an inferred type in your library:
1const shoutItOutLoud = <T extends string>(str: T) => ( 2 `${str.toUpperCase() as Uppercase<T>}!!!` as const 3);
The type of this function isn't just string
, it's the actual result as a TypeScript string literal. But how do you write a test for that behavior of your library?
Now you can write a test for the type for your function!
1import { Expect, Equal } from "type-testing"; 2 3const hello = shoutItOutLoud('hello'); 4type test_hello = Expect<Equal<typeof hello, 'HELLO!!!'>>;
You can do this right alongside your regular tests!
1import { Expect, Equal } from 'type-testing'; 2import { shoutItOutLoud } from './shout'; 3 4describe('shoutItOudLoud', () => { 5 it('has the intended API surface', () => { 6 const hello = shoutItOutLoud('hello'); 7 8 // this tests the type 9 type test_hello = Expect<Equal<typeof hello, 'HELLO!!!'>>; 10 11 // this tests the runtime behavior 12 expect(hello).toEqual('HELLO!!!'); 13 }); 14});
[!IMPORTANT] Now, if your fancy type isn't exactly correct, your test suite will fail!
just like you'd want it to
Consider, though, that many libraries are shipping TypeScript types as part of the product. In this case, they have very little way to be sure that the types they're shipping to their users are working as intended. Consider the above but written in types:
1type ShoutItOutLoud<T extends string> = `${Uppercase<T>}!!!`; 2type Hello = 'hello'; 3 4// Compiler error! this should be `HELLO!!!` (all caps) 5type test_Hello = Expect<Equal<ShoutItOutLoud<Hello>, 'HeLLO!!!'>>
protip: you can see some great examples of what these kinds of test looks like in this very repo itself.
Now, you can write tests that will lock-in the intended behavior of your types.
If someone changes a type parameter or return type accidentally, now you'll know about it right away!
1type test_params = Expect<Equal< 2 Parameters<typeof shoutItOutLoud>, 3 [string] 4>>; 5 6type test_return = Expect<Equal< 7 ReturnType<typeof shoutItOutLoud>, 8 `${Uppercase<string>}!!!` 9>>;
Equal
NotEqual
Expect
ExpectFalse
Extends
IsAny
IsNever
IsTuple
IsUnion
TrueCases
SimpleEqual
FalseCases
Also, if you just install and start using the library you'll discover that every type has lots of JSDoc description to help you along the way!
Lots of reasons.
Nope! It's been knocking around in the TypeScript community for a while, but there has yet to be someone to write tests for these types (ironically) and package them into a micro library.
Expect
and Equal
be combined to a type ExpectEqual
?Unfortunately, no. The power of this approach taken in this library is that it will error at build time while being type checked, and currently there's no way to combine the two utilities.
If you do happen to find a way... we're all waiting to hear about it! File an issue!!
"no API is the best API".
You may have seen a version of this code copied around that contained aliases for Expect
like IsTrue
and ExpectTrue
, as well as aliases for ExpectFalse
like IsFalse
. The hope is to avoid people having to learn or memorize the quirks of different assertion APIs. This is still a pretty cutting-edge part of the TypeScript world, but if you look around, you're going to find that 98% of the time (or more!) you just need Expect
and Equal
.
You might be familiar with other projects that attempt to do something similar. Here's a quick overview:
eslint-plugin-expect-type
is powerful, but relies on comments and requires ESLint. This means that refactoring and renaming will get out of sync because the tests themselves aren't actually code. On top of that, there's a problem with the order of unions in TypeScript not being stable from release-to-release, which causes very annoying false positive test failures that you have to manually fix.tsd
is nice, but it's not for the type layer itself. For that matter, there are a few nice alternatives if you can integrate this into your test runner, e.g. Jest's Expect type is built into it's assertion functions. The same goes for expect-type
.ts-expect
is probably the most similar currently existing thing but it is built more for things that have runtime manifestations (i.e. things that JavaScript, unlike TypeScript types). The code in this library is already battle tested and used by lots of production projects.If you have noUnusedLocals enabled, you can safely disable it just for your tsconfig.eslint.json
. It can still be left on for building for production.
For ESLint, there's a more powerful way to do it which is just to turn off this specific pattern for test files:
1/** @type { import('@typescript-eslint/utils').TSESLint.Linter.Config } */ 2const config = { 3 overrides: [ 4 { 5 files: ['**/*.test.ts', '**/*.test.tsx'], 6 rules: { 7 '@typescript-eslint/no-unused-vars': [ 8 'error', 9 { 10 vars: 'all', 11 varsIgnorePattern: 'test_.*', // TypeScript type tests 12 argsIgnorePattern: '_', 13 }, 14 ], 15 }, 16 }, 17 ], 18};
Well. This level of type testing isn't "for everyone". It's largely for library authors or projects that have very powerful inferred types.
If:
strict
modeany
a lot// @ts-ignore
lines...then this library might not be something you'd benefit from introducing.
But as we start seeing projects like Zod, where the types "working" is fully half of the entire project (or type-fest where it's the whole project).. it starts to feel a little lopsided to be able to write tests and assertions about the JavaScript part, but not as much about the TypeScript part.
expect
?Depending on your testing library, you may be able to do something like this:
1expect<'HELLO!!!'>(hello).toEqual('HELLO!!!');
This approach can work, but there are lots of libraries that operate exclusively at the type level. They could never use such an approach because they don't have any runtime code with which to test.
You also put yourself at the mercy of the error message generated by the expect types. With type-testing, it's quite clear that the types are wrong, and therefore much more clear what you need to fix.
No vulnerabilities found.
No security vulnerabilities found.