【问题标题】:fp-ts and jest: ergonomic tests for Option and Either?fp-ts 和 jest:Option 和 Either 的人体工程学测试?
【发布时间】:2020-04-25 01:17:49
【问题描述】:

我正在使用fp-ts,并使用 Jest 编写单元测试。在许多情况下,我正在测试可为空的结果,通常用OptionEither 表示(通常是数组finds)。如果结果为 none(以 Option 为例),并且知道这个结果是 some 继续下去,那么最符合人体工程学的方法是什么?

以下是我目前如何解决问题的示例:

function someFunc(input: string): Option.Option<string> {
  return Option.some(input);
}

describe(`Some suite`, () => {
  it(`should do something with a "some" result`, () => {
    const result = someFunc('abcd');

    // This is a fail case, here I'm expecting result to be Some
    if(Option.isNone(result)) {
      expect(Option.isSome(result)).toEqual(true);
      return;
    }

    expect(result.value).toEqual('abcd');
  });
});

但是必须写一个提前返回的 if 不太符合人体工程学。

我也可以写一个as 断言:

  // ...
  it(`should do something with a "some" result`, () => {
    const result = someFunc('abcd') as Option.Some<string>;

    expect(result.value).toEqual('abcd');
  });
  // ...

但缺点是我必须重写some 的类型。在许多情况下,编写它很繁重,需要编写和导出接口仅用于测试目的(这也不符合人体工程学)。

有没有办法简化这种测试?

编辑:这是一个更接近真实情况的测试用例:


interface SomeComplexType {
  id: string,
  inputAsArray: string[],
  input: string;
}

function someFunc(input: string): Option.Option<SomeComplexType> {
  return Option.some({
    id: '5',
    inputAsArray: input.split(''),
    input,
  });
}

describe(`Some suite`, () => {
  it(`should do something with a "some" result`, () => {
    const result = someFunc('abcd');

    // This is the un-ergonomic step
    if(Option.isNone(result)) {
      expect(Option.isSome(result)).toEqual(true);
      return;
    }

    // Ideally, I would only need this:
    expect(Option.isSome(result)).toEqual(true);
    // Since nothing will be ran after it if the result is not "some"
    // But I can imagine it's unlikely that TS could figure that out from Jest expects

    // Since I now have the value's actual type, I can do a lot with it
    // I don't have to check it for nullability, and I don't have to write its type
    const myValue = result.value;

    expect(myValue.inputAsArray).toEqual(expect.arrayContaining(['a', 'b', 'c', 'd']));

    const someOtherThing = getTheOtherThing(myValue.id);

    expect(someOtherThing).toMatchObject({
      another: 'thing',
    });
  });
});

【问题讨论】:

  • 我不确定我是否跟随。什么是有效案例? Option.isNone(result)true?还是什么?
  • 我已经更新了描述,希望能更清楚。这个测试用例假设结果应该是some(根据函数的内容)
  • expect(Option.isSome(result)).toEqual(true)?
  • @Lee 这是一种解决方法,可以在运行测试时出现更好的错误。我确实期待Option.isSome(result)true,因为这个测试需要some!但是如果我只写这个断言,而测试运行良好(这个断言失败),Typescript 会对下一行不满意,因为它不会仅仅因为断言而将result 视为some
  • 你可以使用elem: expect(elem('abcd', result)).toEqual(true)

标签: typescript testing jestjs fp-ts


【解决方案1】:

你可以像这样写一个不安全的转换fromSome

function fromSome<T>(input: Option.Option<T>): T {
  if (Option.isNone(input)) {
    throw new Error();
  }
  return input.value;
}

然后在测试中使用它

  it(`should do something with a "some" result`, () => {
    const result = someFunc('abcd');
    const myValue = fromSome(result);
    // do something with myValue
  });

【讨论】:

  • 听起来很不错!唯一的缺点是错误没有显示在测试流程中,但是由于该行是由 Jest 显示的,所以不难看出哪里出错了。
  • 如果你只返回 none 而不是抛出错误怎么办?在这里查看我的建议:github.com/gcanti/fp-ts/issues/1096
  • 你的建议和toUndefined一模一样。这就是@ford04 的原始答案。
【解决方案2】:

toNullabletoUndefined 怎么样?给定Option&lt;string&gt;toNullable 返回string | null

import { toNullable, toUndefined } from "fp-ts/lib/Option";

it(`should do something with a "some" result`, () => {
  expect(toNullable(someFunc("abcd"))).toEqual("abcd");
});

expect(Option.isSome(result)).toEqual(true) 的问题是,类型守卫isSome 不能用于缩小expect 外部代码路径中的result(参见here 控制流分析的工作原理)。

您可以使用更精简的assertion functions 并将它们与fp-ts 类型保护结合起来,例如:

import { isSome, Option } from "fp-ts/lib/Option"

function assert<T>(guard: (o: any) => o is T, o: any): asserts o is T {
  if (!guard(o)) throw new Error() // or add param for custom error
}

it(`a test`, () => {
  const result: Option<string> = {...}
  assert(isSome, result)
  // result is narrowed to type "Some" here
  expect(result.value).toEqual('abcd');
});

我不知道是否有一种使用类型保护签名来增强 Jest expect 函数类型本身的好方法,但我怀疑它是否简化了您的案例,而不是简单的断言或以上解决方案。

【讨论】:

  • 它确实适用于一个测试,但它并没有使对结果值进行多次测试更容易,这就是我的目标。我在问题正文中添加了一个更接近真实测试的示例。
【解决方案3】:

这个问题在这一点上有点老了,并且有一个可以接受的答案,但是有几个非常好的库可以让测试 fp-ts EitherOption 非常愉快。它们都工作得很好,我真的无法决定我更喜欢哪个。

它们允许你写这样的东西:

test('some test', () => {
  expect(E.left({ code: 'invalid' })).toSubsetEqualLeft({ code: 'invalid' })
})

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-16
    • 1970-01-01
    • 1970-01-01
    • 2022-07-19
    • 1970-01-01
    • 2021-05-10
    • 1970-01-01
    相关资源
    最近更新 更多