enhetstesting - Hvordan teste generiske typer i Typescript? Ikke en implementering av den generiske typen, men som en typeguard?
Jeg jobber med å utvide en bunke Typescript-typer til et verktøybibliotek som et personlig «forstår jeg virkelig Typescript?»-prosjekt.
Jeg har bare MANGE problemer med å finne ut hvordan jeg skal enhetsteste den forbannede tingen.
Problemet er at jeg vil teste at når jeg sender inn feil type til en funksjon som krever en type, vil TSC fange det. Noe den gjør. Glimrende. Men det er en liten feil med det, nemlig at enhetstestbiblioteket jeg bruker (Jest) vil kaste en feil (ønsket atferd) og deretter nekte å kjøre resten av testpakken fordi det kastet en feil. Jeg kan ikke engang bruke "vil kaste feil" fordi det ikke er en Javascript-feil, men en Typescript-feil.
Her er koden jeg har så langt. Jeg bruker Jest, men løsningen min trenger ikke å være spesifikk for Jest, jeg har en merkelig følelse av at jeg kanskje trenger en slags spesialisert testbibliotek.
// src/Either/Either.ts
export type Either<Base, Either, Or> = (Base & Either) | (Base & Or);
// src/either/Either.spec.ts
import { Either } from "./Either";
interface Pet {
name: string;
}
interface Dog {
says: "awoo" | "ruff" | "yip" | "bork" | "bark" | "grr";
willFetch: true;
}
interface Cat {
says: "meow" | "hiss";
willFetch: false;
}
describe("Either<Base, Either, Or>", () => {
it("allows one extension OR the other, but not both, and not neither", () => {
type CommonPet = Either<Pet, Dog, Cat>;
const whatAmI = (pet: CommonPet): "dog" | "cat" => {
if (pet.willFetch === true) {
return "dog";
}
return "cat";
};
expect(whatAmI({ name: "Rex", willFetch: true, says: "awoo" })).toBe("dog");
expect(whatAmI({ name: "Psycho", says: "hiss", willFetch: false })).toBe(
"cat"
);
// these lines will throw a typescript error (desired)
/*
const freak = whatAmI({ name: "Freak", says: "meow", willFetch: true });
const uncommonPet = whatAmI({ name: "Nagini", says: 'hiss', willFetch: false, isSnake: true });
*/
// but how to I tell Jest that I WANT it to throw a typescript error?
interface AristoCat {
knowsWhereItsAt: boolean;
}
interface JellicleCat {
nightmareFuel: number;
}
type MusicalCat = Either<Pet & Cat, AristoCat, JellicleCat>;
const isGood = (cat: MusicalCat): boolean | never => {
if ("knowsWhereItsAt" in cat) {
return true;
}
if (cat.nightmareFuel > 0) {
throw new Error("NOOOOOO!");
}
return false;
};
expect(
isGood({
name: "Duchess",
says: "meow",
willFetch: false,
knowsWhereItsAt: true
})
).toBe(true);
expect(
isGood({
name: "MacCavity (stage)",
willFetch: false,
says: "meow",
nightmareFuel: 0
})
).toBe(false);
const filmIt = () =>
isGood({
name: "MacCavity",
willFetch: false,
says: "meow",
nightmareFuel: 9001
});
expect(filmIt).toThrowErrorMatchingInlineSnapshot(`"NOOOOOO!"`);
// again, we expect this to throw in TYPESCRIPT because it has BOTH.
/*
const dreamIHadOnAcid = isGood({
name: 'Cheshire',
says: 'meow',
willFetch: false,
knowsWhereItsAt: true,
nightmareFuel: 5/7
})
*/
// again, we expect this to throw in TYPESCRIPT because it has NEITHER.
/*
const dreamIHadOnAcid = isGood({
name: 'Tardar Sauce',
says: 'meow',
willFetch: false,
})
*/
});
});
-- REDIGER:
Det er en ekstra vanskelighet. Jeg er ikke sikker på om Either-typen min fungerer som den skal (eller i så fall, om kompilatoren kan oppdage det).
Jeg bruker Typescript-lekeplassen til dette...
export type Either<Base, Either, Or> = (Base & Either) | (Base & Or);
type Pet = {
name: string;
says?: string;
}
type DogExtension = {
playHours: number;
}
type CatExtension = {
sleepHours: number;
}
type CommonPet = Either<Pet, DogExtension, CatExtension>;
const dog: CommonPet = { name: "rex", says: "awoo", playHours: 5 };
const cat: CommonPet = { name: "manx", says: "meow", sleepHours: 14 };
const testPet = (testName: string, pet: CommonPet): CommonPet => {
return pet
};
testPet("dog should pass", dog);
testPet("cat should pass", cat);
testPet("catdog should not pass", { ...dog, ...cat }); /* should throw a type error, but doesn't */
Dette kan være en feil i TypeScript-kompilatoren... og en enkel å gjøre. Hund er et CommonPet, Katt er et CommonPet, så hvorfor skulle ikke {...dog, ...cat} være et vanlig kjæledyr? Med mindre - den langt mer sannsynlige løsningen, min Either =(T&E) | (T&O) bare ikke fungerer slik jeg trodde det ville.
Svar #1
Jeg har stått overfor dette problemet før og klarte å få det til å fungere med dtslint. Jeg syntes disse testene var veldig verdifulle for å skrive biblioteker som inkluderer mye generisk typelogikk fordi jeg stadig havnet i situasjoner der det å fikse én ting førte til at typer ble tolket feil andre steder.
Her er repoet hvor jeg klarte å få det til å fungere. Spesielt inneholder denne filen en av testene. Typetester kan kjøres med yarn type-check eller npm run type-check.
Når noe feiler, får du en feil som dette:
Error: /good-form/types/Field.types.ts:19:3
ERROR: 19:3 expect TypeScript@local expected type to be:
number
got:
string
Svar #2
Jeg gjorde det samme som deg, for å lære TypeScript. Jeg har en haug med enhetstester her. Biblioteket i seg selv gjør omtrent det du ønsker å oppnå. Så jeg anbefaler at du begynner å skrive din strenge Equals-type, for å sjekke typeresultatene dine.
