Typescript Test Your Generic Type Part 1
Tags: webdev, programming, typescript, tutorial
Published: 2022-06-28 20:57:49

when trying to create tools with typescript, especially aiming for type safety and flexibility, it is common that we end up with many generic types,

some of it even end up with a big chunk of type manipulation logic.

So how can we be confident that our types are working, how can we test our types?

Turn out it is simple and also hard, but we will focus on the simple part first.

let's take [string literal type substring counting](https://dev.to/tylim88/typescript-count-substring-of-a-string-literal-type-536b) as our test subject

```ts
type GetCountOfSubString<
    String_ extends string,
    SubString extends string,
    Count extends unknown[] = []
> = String_ extends `${string}${SubString}${infer Tail}`
    ? GetCountOfSubString<Tail, SubString, [1, ...Count]>
    : Count['length']


type NumberOfA = GetCountOfSubString<"a--a--aa--a","a"> // 5
```

We want to make sure `GetCountOfSubString<"a--a--aa--a","a">` always result in `5`

basically both should extend each other

next, we create the checker, the checker consist of 2 parts

first is Expect, we want to check whether both types extends each other

```ts
type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type r1 = Expect<GetCountOfSubString<"a--a--aa--a","a">,5> // true, success check
type r2 = Expect<GetCountOfSubString<"a--a--aa--a","a">,1> // false, fail check
```
[playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hwGED2BXAdsA8gMwMooCNdgAnASzQHMAeAKCgamPKoH0oIAPYCNAEwDOUAaQqUANPUb4ioqh269BwuRKkNk6YAp78h6ANZokAdzQBtALpQAvFCu0AfLaar2XXcoAGAEgDeIiyUAL7+MsxioX4U2BAkUAAqAIZkADbBXupQAPyw8JoYOOGq1Mlp4kyEEVQV5gCMFQB0zQXAlo5ZAFxQreYA5Km8lMAAFn2WtLSgkFAAopyQAMbApRUAqo52CTpKQms5UPseu4kHpCjQ3dhJqQKXUNe30JNT4NAkdS7zSytwiKiFPBVEoAIiSAFpwRCodCkiDxGCQY5xABWZwAenRUHOEAqYCSAiEPBErxmJAATF8FhBltQ-q0isCgtQwZDYbD4YjkXUMVjHncKtc0tiICIgA)

![type test 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uimd19z9keg2w0om0prc.png)
 
 

so far so good, you get the result you want, the type is true if the result is correct and false if the result is incorrect

but something is missing, when you run type-check with tsc, 
nothing happens, this is because it simply returns the type as true and false, and nothing is invalid about it, so typescript does not complain.

so we need 2nd part, the assertion

```ts

type Assert<T extends true> = T // be anything after '=', doesn't matter

```

applying them

```ts
type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type Assert<T extends true> = T // be anything after '=', doesn't matter

type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test
```
![type test 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbscxxtbxzyo9gfxdity.png)

now we see that the fail test failed, and when we run tsc, we can see the error in the console.

but wait something is still not right, what is it?

well, a fail test should fail, that is expected, and should not trigger an error

so are we back to square one?

no, we are closer, here is how we solve it, by using the `@ts-expect-error` comment

```ts
type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test
// @ts-expect-error
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test
```

[playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hwGED2BXAdsA8gMwMooCNdgAnASzQHMAeAKCgamPKqggA9gI0ATAZyh9SFSgBp6jfEWGsOXXgKEsxEhsnTA2nbvyjoA1miQB3NAG0AulAC8US7QB8NpjMpb5ugAYASAN5KRAF8-KWYgvwpsCBIoABUAQzIAG0DPVSgAflh4dQwcUNdqBOTRJkIwqlKzAEZSgDoG3OALB3SALigmswByJO5KYAALbotaWlBIKABRdkgAY2Ai0oBVB1tY9x0BZcyoHbktuN3SFGgO7HikvjOoC6voMYnoAEE+a5JFjYOFKBOIJ3WUAA9ECoARoPE0CAhiIoPFsFwYt1rN1SjwkBA+GhupoALbxYCIx7gaAkarOV7vRYzeaLOCIVB5PDlQoAIniAFoOZzuTz4qzROzWQ5RABWBxOEG-EinUpgeJvX6Y4C0KUAAWAfA5HFp2pIJCQJHGJKgJAATBS3tFqbMIAtqPSmvkWcpqOyuXy+QKhSLqhLgaC7tdShdkkqhGMxkA)

![type test 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjd2vyymx1gjg4l2nz6l.png)

there, no more type-checking error

`@ts-expect-error` only suppress the error if the line has an error, else if you use it on a perfectly ok line, TS will give us an error instead, and this is the behaviour that we want

so let's see if there is a bug in GetCountOfSubString, will this works as expected?

let's try to fail our pass test:
```ts
type GetCountOfSubString<
    String_ extends string,
    SubString extends string,
    Count extends unknown[] = []
> = "BUG!!"

type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type Assert<T extends true> = T // be anything after '=', doesn't matter

type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test
// @ts-expect-error
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test
```
![type test 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f80u3w0omgpt3jbvj8lb.png)

 
[playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hwGED2BXAdsA8gMwMooCNdgAnASzQHMAeAKCgamPKoH0oIAPYCNAEwDOUAaQqUANPUb4ioqh269BwuRKkNk6YAp78h6ANZokAdzQBtALpQAvFCu0AfLagAiAEIBVGAEIfr2lpQSCgAUU5IAGNgagAVcShPRztYnSUhTygAfkS0vShUnNIUaAAuKGwAQwAbATKKmrqoQODoAEEBOpIY1K5dZWKIZxSoAHpRqAJoSrQQYAALMShK7B4SKAByGw2EviQIATQN7QBbSuA1lvBoEgBGFw6umPComLhEVAwcGWYxaldKgBaQFAkGgyqucQA1yOcQAVkcznGUEGCTAlU6KIOwFoyIAAsABICuK9iSQSEgSEFrlASAAmB6dCDdagvCDRajvTRfPCEX5Uf7g4GVcGQ6Gw26IsYTKq1CAJKpkapYkSBQJAA)

let's try to fail our fail test:

```ts
type GetCountOfSubString<
    String extends string,
    SubString extends string,
    Count extends unknown[] = []
> = 1

type Expect<T, U>= T extends U ? U extends T ? true : false : false 

type Assert<T extends true> = T // be anything after '=', doesn't matter

type r1 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,5>> // true, pass test

// @ts-expect-error
type r2 = Assert<Expect<GetCountOfSubString<"a--a--aa--a","a">,1>> // false, fail test
```

![type testing 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k026xklpg6wkpbz7lqlq.png)

[playground ](https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hwGED2BXAdsA8gMwMooCNdgAnASzQHMAeAKCgamPKqggA9gI0ATAZyh9SFSgBp6jfEWGsOXXgKEsxEhsnTA2nbvyjoA1miQB3NAG0AulAC8US7QB8NqAEZatUJCgBRdpADGwNQAKqJQAKoOtsFa8rrhUAD8EbE6AjHJpCjQAFxQ2ACGADZ8ufnFpVDuntAAgnylJEExcmlQWRBO0VAA9D1QBNAFaCDAABYiUAXYXCRQAOTW82E8SBB8aPOaALYFwLPV4NAkLs71jUG+AUFwiKgYOFLMItQARAUAtB+f3z8Fr6J3q8HKIAKwOJx9dokbJhMAFBrtdbAdxQgACwD4Hw412xJBISBIHiOUBIACYzg0IE1qFcIIFqLd1A88IRnlQ3n8vgU-gCgSCXBDev1CiUIGFCmQikihO53EA)

yup, it works!

but we are not done yet, if you are using linter like eslint, it will complains the type is declared but never used

![type test 5](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/etrnmidn4fvl481jo020.png)

there are 2 ways to solve it:

first we can export them
![type test 6](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/66f1h0tk4qu5ernrwjp0.png)

or we turn Assert into a function instead

```ts
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const assert = <T extends true>() => {
	//
}

assert<Expect<GetCountOfSubString<'a--a--aa--a', 'a'>, 5>>() // true, pass test
// @ts-expect-error
assert<Expect<GetCountOfSubString<'a--a--aa--a', 'a'>, 1>>() // false, fail test
```
[playground](https://www.typescriptlang.org/play?#code/C4TwDgpgBA4hwGED2BXAdsA8gMwMooCNdgAnASzQHMAeAKAEhjyqB9KCAD2AjQBMBnKP1IVKAGgb4iIquy48BQmeIbJ0wOdz6D0AazRIA7mgDaAXSgBeKOdoA+K1Cai2nLYoAGAEgDew5pQAvr5SzlTBPhTYECRQACoAhmQANoEeDAD8sPBqGDihytSJKWJOhGHiNgCMpQB09bnAZnYMAFxQjSYA5Mk8lMAAFl1mtLSgkFAAohyQAMbARaUAqg7WcZoKgktQWQAU226b8TtQpCjQ7dgJyfwQAJRQl9e3owD0r+z8yRTAALS8ZH4CQIvV+aDcv2+4KgAAFxhB+LNyGA-giocBXgZfugULdeL8AG4JEj8WicMBIEgaWZINDCKAJfi3KmOIobbSnEjnOy7B6WBw+BjvWiBUaM5kLaZzBZwRCoPJ4cqFLoJX6-VXqjUJLqlFVdOylACsdh5D3enPOpTA4tOCOAtHNcP4v3JEHmLpIJEptHFMUlMzdMpy8qwiukAWoKrVWq1OqgeoNUCqJt5UHNVxuEFKVxStuEQA) 

the second method is recommended, it is shorter because it doesn't require us to create a new type for every assertion 

that is it for part 1, in [part 2](https://dev.to/tylim88/typescript-test-your-generic-type-part-2-k2b) we will take care of some edge cases, which is the hard part