richardscarrott / ok-computer

λ "Functions all the way down" data validation for JavaScript and TypeScript.
MIT License
79 stars 0 forks source link

Jest matcher #25

Open richardscarrott opened 2 years ago

richardscarrott commented 2 years ago

I recently needed to write some tests which made assertions on an API response which did not have fixtures (i.e. e2e tests). This meant I was testing types more than literal values.

The jest assertions were quite cumbersome and I found myself wanting to reach for OK Computer. e.g.

import {
  assert,
  object,
  array,
  string,
  integer,
  or,
  nul,
  create,
  is,
} from 'ok-computer';
import 'jest-extended';
const matchers = require('jest-extended');
expect.extend(matchers as any);

// Assume this comes from a production API so varies in length and data
const actual = {
  status: 200,
  data: {
    drivers: {
      nodes: [
        {
          id: 'dvr_1',
          name: 'Lewis Hamilton',
          team: 'Mercedes',
          carNumber: 44,
        },
        {
          id: 'dvr_2',
          name: 'Max Verstappen',
          team: 'Red Bull Racing',
          carNumber: 33,
        },
        {
          id: 'dvr_3',
          name: 'George Russell',
          team: null,
          carNumber: null,
        },
        {
          id: 'dvr_4',
          name: 'George Russell',
          team: null,
          carNumber: null,
        },
      ],
    },
  },
  headers: {
    'Content-Length': 100,
  },
  errors: [{ code: 'FOO_BAR' }],
};

test('Jest matchers', () => {
  expect(actual).toEqual({
    status: 200,
    data: {
      drivers: {
        nodes: expect.any(Array),
      },
    },
    headers: expect.anything(),
    errors: expect.any(Array),
  });
  actual.data.drivers.nodes.forEach(driver => {
    expect(driver).toEqual({
      id: expect.any(String),
      name: expect.any(String),
      team: expect.toBeOneOf([expect.any(String), null]),
      carNumber: expect.toBeOneOf([expect.any(Number), null]),
    });
  });
});

test('OK Computer', () => {
  const any = create(() => true)('Expected any'); // TODO: Add to OK Computer lib

  const validator = object({
    status: is(200),
    data: object({
      drivers: object({
        nodes: array(
          object({
            id: string,
            name: string,
            team: or(nul, string),
            carNumber: or(nul, integer),
          })
        ),
      }),
    }),
    headers: any,
    errors: array(any),
  });

  assert(actual, validator);
});

I expect the error output is better using the jest matchers, but perhaps a custom jest matcher could handle that?

expect(actual).toBeOkay(validator)

With OK Computer you also get type inference

import { assert, fn } from 'ok-computer';
const actual: unknown = {};
assert(actual, fn);
actual();