fp-ts и jest: ergonomi c тесты для Option и Either? - PullRequest
1 голос
/ 07 января 2020

Я использую fp-ts, и я пишу юнит-тесты с Jest. Во многих случаях я тестирую обнуляемые результаты, часто представленные Option или Either (обычно массив find s). Какой самый эргономичный c способ заставить тест провалиться, если результата нет (взяв Option в качестве примера), и продолжать знать, что этот результат какой-то?

Вот пример как я могу решить проблему в данный момент:

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 с ранним возвратом не очень эргономична c.

Я мог бы альтернативно написать as утверждение:

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

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

Но недостатком здесь является то, что я должен переписать тип some. Во многих случаях необходимость писать это тяжело, требует написания и экспорта интерфейса с единственной целью тестирования (что также не является эргономикой c).

Есть ли способ упростить этот вид тестирования ?

Редактировать: Вот тестовый пример, который ближе к реальным условиям:


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

Ответы [ 2 ]

2 голосов
/ 08 января 2020

Вы можете написать небезопасное преобразование 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
  });
2 голосов
/ 07 января 2020

Как насчет toNullable или toUndefined? Учитывая Option<string>, 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) заключается в том, что тип guard isSome не может использоваться для сужения result во внешнем коде путь expect (см. здесь , как работает анализ потока управления).

Вы можете использовать функции подтверждения , которые немного скуднее и комбинировать их с 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 с помощью сигнатуры охранника типа, но я сомневаюсь, что это упрощает ваш случай вместо простое утверждение или решения выше.

Надеюсь, это поможет!

...