【问题标题】:How to test generic types in Typescript? Not an implementation of the generic type, but as a typeguard?如何在 Typescript 中测试泛型类型?不是泛型类型的实现,而是作为类型保护?
【发布时间】:2020-04-17 01:52:22
【问题描述】:

我正在将一堆 typescript 类型扩展为实用程序库,作为个人“我真的了解 Typescript 吗?”项目。

我只是在弄清楚如何对这该死的东西进行单元测试时遇到了很多麻烦。

问题是我想测试当我将错误的类型传递给一个调用类型的函数时,TSC 会捕获它。它确实如此。辉煌。但是有一个很小的缺陷,我使用的单元测试库(Jest)会抛出一个错误(期望的行为),然后拒绝运行测试套件的其余部分,因为它抛出了一个错误。我什至不能使用“将抛出错误”,因为它不是 Javascript 错误而是 Typescript 错误。

这是我到目前为止的代码。我正在使用 Jest,但我的解决方案不需要特定于 Jest,我有一种有趣的感觉,我可能需要某种专门的测试库。

// 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, 
    })
    */
  });
});

-- 编辑:

还有一个难度。因为我不确定我的 Either 类型是否按应有的方式工作(或者如果是,编译器无法拾取它。)

我正在为此使用 Typescript 游乐场...

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 */

这可能是打字稿编译器中的一个错误......而且很容易制作。 Dog 是 CommonPet,Cat 是 CommonPet,那么为什么 {...dog, ...cat} 不是普通宠物?除非 - 更有可能的解决方案,我的要么 =(T&E) | (T&O) 只是不像我想象的那样工作。

【问题讨论】:

  • 我以前没有听说过你的打字测试。你确定这是你想做的吗?测试通常用于实际运行您的代码,并确保它按照您的想法运行。但是您可以依靠编译器为您强制执行类型安全。
  • 是的,这正是我想做的。此代码不适用于现有代码库。基本上我想做的是编译一个泛型类型列表(如上面的“Either”),并让它们作为 NPM/Yarn 库作为实用程序类型导入。这个想法是,它将成为“类型的 Lodash”。 Utility Types typescriptlang.org/docs/handbook/utility-types.html 涵盖了许多类型 - 但存在一些差距,就像重新创建 lodash 帮助我在 5 年前学习 JS 一样,创建“loTypes”将帮助我和其他人更好地理解泛型。
  • 明白。谢谢你启发我!

标签: typescript unit-testing typescript-generics


【解决方案1】:

我之前遇到过这个问题,并设法让它与dtslint 一起工作。我发现这些测试对于编写包含大量泛型类型逻辑的库非常有价值,因为我经常遇到这样的情况:修复一件事会导致在其他地方错误地推断类型。

这是the repo 我设法让它工作的地方。具体来说,this file 包含其中一项测试。可以使用yarn type-checknpm run type-check 运行类型测试。

当某事失败时,你会得到这样的错误:

Error: /good-form/types/Field.types.ts:19:3
ERROR: 19:3  expect  TypeScript@local expected type to be:
  number
got:
  string

【讨论】:

    【解决方案2】:

    我和你一样,学习 TypeScript。我有一堆单元测试here。库本身可以完成您想要实现的目标。所以我建议你开始编写严格的Equals 类型,以检查你的类型结果。

    【讨论】:

      猜你喜欢
      • 2019-05-30
      • 2021-12-06
      • 2014-08-19
      • 2020-06-11
      • 1970-01-01
      • 1970-01-01
      • 2013-08-16
      • 1970-01-01
      • 2019-10-06
      相关资源
      最近更新 更多