node package manager

45

45

A functionally-oriented test runner

45 is the fast and functional test runner that is easy to use and gets out of your way.

Let me have it!

npm install --save-dev 45

Features

  • Does not rely on globals
  • Test failure if no assertions are returned
  • Lazy, monadic, and curried test assertions via 4.5
  • Promise, Observable, and Async/Await support
  • Runs all tests in parallel
  • ES2015 and TypeScript support out-of-box

Basic Usage

Create a test file

// test/foo.js 
 
// ES2015 
import { describe, given, it, equals } from '45';
 
export const test = describe('Array', [
  given('a few numbers', [
    it('has length greater than 0', () => {
      return equals([1, 2, 3].length > 0, true);
    })
  ])
])
 
export const otherTest = it('equals 4', () => {
  return equals(4, 4);
})
 
// commonjs 
const { describe, given, it, equals } = require('45');
 
exports.test = describe('Array', [
  given('a few numbers', [
    it('has length greater than 0', () => {
      return equals([1, 2, 3].length > 0, true);
    })
  ])
])
 
exports.otherTest = it('equals 4', () => {
  return equals(4, 4);
})

In your terminal run

./node_modules/.bin/45 test/foo.js
# Supports globs 
./node_modules/.bin/45 test/*.js
 
# or without parameters 
# by default it will search for all .test and .spec files in src/ folder 
# and for all files in test/ and tests/ folders 
./node_modules/.bin/45

And you should see:

basic-test

Tests

When running 45 from the command line, it will look for all test files it can, collecting all exports, of no particular export name, that adhere to the Test interface described in the types section. This means that 45 can be extended to handle new test types not offered here via 3rd party libraries.

All 45 Tests must return objects adhering to the Assertion interface described in the Types section. Many assertions are re-exported by this library from 4.5. Though a number are provided by default -- 3rd party libraries implementing the Assertion interface can be used 100% freely.

describe(thing: string, tests: Array<Test>): Test

Allows collecting many Tests together as a larger whole. All tests are run in parallel.

import { describe } from '45';
 
export const test = describe('My Thing', [ ... ])

given(parameters: string, tests: Array<Test>): Test

Just like describe in that it allows collecting many Tests together as a larger whole, but allows for more descriptive test suites. All tests are run in parallel.

import { describe, given } from '45';
 
export const test = describe('My thing', [
  given(`a b and c`, [
    it('does ...', () => { ... })
  ])
])

it(does: string, testFn: TestFn): Test

Primitive test type which allows providing a callback to actually perform assertions. By default, will timeout at 2000 milliseconds.

import { it, pass } from '45';
 
export const test = it('does things', () => {
  return pass(1);
})

timeout(ms: number, test: Test): Test

Allows adjusting the amount of time a test can take to complete.

import { it, timeout, equals } from '45';
 
export const failing = it('fails', () => {
  return new Promise((resolve) => {
    setTimeout(resolve, 3000, 1)
  })
    .then(equals(1));
})
 
export const passing = timeout(3500, it('passes', () => {
  return new Promise((resolve) => {
    setTimeout(resolve, 3000, 1)
  })
    .then(equals(1));
}));

beforeEach(hook: () => any, tests: Array<Test>): Test

Allow running a hook before a series of tests. This will run the containing array of tests one after another.

import { beforeEach, describe, given, it } from './';
 
import { equals } from '4.5';
 
let x = 0;
 
export const test = describe('beforeEach', [
  given(`a function and an array of tests`, [
    beforeEach(() => { x++; }, [
      it('runs beforeEach test', () => {
        return equals(1, x);
      }),
 
      it('runs beforeEach test every time', () => {
        return equals(2, x);
      }),
    ]),
  ]),
]);

Assertions (re-exported from 4.5)

  • All functions of arity 2 or more are curried.
  • All types are defined below in the types section.

equals<A>(expected: A, actual: A): Assertion<A>

Asserts two values expected and actual to have value equality.

is<A>(expected: A, actual: A): Assertion<A>

Asserts two values expected and actual to have referential equality.

pass<A>(value: A): Assertion<A>

Creates an assertion which always passes with a given value.

fail(message: any): Assertion<void>

Creates an assertion which will always fail with a given message.

throws(fn: () => any): Assertion<Error>

Creates an assertion that tests that a given function throws and error.

Assertion combinators (re-exported from 4.5)

map<A, B>(fn: (a: A) => B, assertion: Assertion<A>): Assertion<B>

Given a function it maps one assertion value to another.

import { it, map, equals } from '45';
 
export const test = it('maps a value from type A to type B', () => {
  const add1 = (x: number) => x + 1;
 
  return map(add1 /* called with 1 */, equals(1, 1)) // Assertion<2>;
});

ap<A, B>(fn: Assertion<(a: A) => B>, value: Assertion<A>): Assertion<B>

Given an assertion containing a function from a to b, and an assertion of a returns an assertion of type b.

import { it, ap, pass } from '45';
 
export const test = it('applys fn to a value', () => {
  const add1 = (x: number) => x + 1;
 
  const fn: Assertion<(x: number) => number> = pass(add1);
  const value: Assertion<number> = pass(1);
 
  return ap(fn, value); // returns an assertion containing the value 2
});

chain<A, B>(fn: (a: A) => Assertion<B>, assertion: Assertion<A>): Assertion<B>

Given a function from one value a to Assertion b and an assertion of type a, returns an assertion of type b Useful for making many assertions using the values from the previous assertion.

import { it, chain, equals, map }
 
export const test = it('chains many assertions', () => {
  const add1 = (x: number) => x + 1;
 
  const oneIsOne = equals(1, 1);
  const isTwo = equals(2); // don't forget, all assertions are curried!
  const isThree = equals(3);
 
  return chain(isThree, map(add1, chain(isTwo, map(add1, oneIsOne))));
})

bimap

Type signature is to long for the header 😄

bimap<A, B>(
  failure: (message: string) => string,
  success: (a: A) => B,
  assertion: Assertion<A>): Assertion<B>;

Similar to map but also allows adjusting error messages.

Useful for creating your own error messages.

import { bimap, equals, fail, pass } from '45';
 
export const test = it('allows adjusting error message', () => {
  return bimap(() => 'Sadly 1 is not correct', fail /* should not pass*/, fail(1));
});

concat(one: Assertion<A>, two: Assertion<A>): Assertion<A>

Chain together 2 assertions.

import { concat, pass, it } from '45';
 
export const test = it('concatenates', () => {
  return concat(pass(1), pass(2)); // passes if and only if both assertions pass.
});

Types

// 45 interfaces
export interface Test {
  name: string;
  timeout: number;
  run(): Promise<Assertion<any>>;
  showStatus: boolean;
}
 
export type TestFn =
  (() => Assertion<any>) |
  (() => Promise<Assertion<any>>) |
  (() => Observable<Assertion<any>>);
 
export interface TestResult {
  failures: number;
  message: string;
}
 
export type Observable<A> =
  {
    subscribe(observer: Observer<A>): Subscription;
  }
 
export type Observer<A> =
  {
    next(value: A): any;
    error(err: any): any;
    complete(): any;
  }
 
export type Subscription =
  {
    unsubscribe(): any;
  }
 
// 4.5 re-exported interfaces
 
export interface Assertion<T> {
  verify(verification: Verification<T>): void;
}
 
export interface Verification<T> {
  success(actual: T): any;
  failure(message: string): any;
}