Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/tylors/45

A Functional, monadic test-runner
https://github.com/tylors/45

assertions functional-programming lazy monad test test-runner

Last synced: 18 days ago
JSON representation

A Functional, monadic test-runner

Awesome Lists containing this project

README

        

# 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!
```sh
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`](https://github.com/TylorS/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

```js
// 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

```sh
./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](./.assets/basic_test.png)

## 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](#types) section. This means
that 45 can be extended to handle new test types not offered here via 3rd
party libraries.

All 45 `Test`s must return objects adhering to the `Assertion` interface
described in the [Types](#types) section. Many assertions are re-exported by this
library from [4.5](https://github.com/TylorS/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`

Allows collecting many `Test`s together as a larger whole. All tests are
run in parallel.

```typescript
import { describe } from '45';

export const test = describe('My Thing', [ ... ])
```

#### `given(parameters: string, tests: Array): Test`

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

```typescript
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.

```typescript
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.

```typescript
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`

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

```typescript
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](#types) section.

#### `equals(expected: A, actual: A): Assertion`

Asserts two values `expected` and `actual` to have value equality.

#### `is(expected: A, actual: A): Assertion`

Asserts two values `expected` and `actual` to have referential equality.

#### `pass(value: A): Assertion`

Creates an assertion which always passes with a given value.

#### `fail(message: any): Assertion`

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

#### `throws(fn: () => any): Assertion`

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

## Assertion combinators (re-exported from 4.5)

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

Given a function it maps one assertion value to another.

```typescript
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(fn: Assertion<(a: A) => B>, value: Assertion): Assertion`

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

```typescript
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 = pass(1);

return ap(fn, value); // returns an assertion containing the value 2
});
```

#### `chain(fn: (a: A) => Assertion, assertion: Assertion): Assertion`

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.

```typescript
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 :smile:

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

Similar to `map` but also allows adjusting error messages.

Useful for creating your own error messages.

```typescript
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, two: Assertion): Assertion`

Chain together 2 assertions.

```typescript
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

```typescript
// 45 interfaces
export interface Test {
name: string;
timeout: number;
run(): Promise>;
showStatus: boolean;
}

export type TestFn =
(() => Assertion) |
(() => Promise>) |
(() => Observable>);

export interface TestResult {
failures: number;
message: string;
}

export type Observable =
{
subscribe(observer: Observer
): Subscription;
}

export type Observer =
{
next(value: A): any;
error(err: any): any;
complete(): any;
}

export type Subscription =
{
unsubscribe(): any;
}

// 4.5 re-exported interfaces

export interface Assertion {
verify(verification: Verification): void;
}

export interface Verification {
success(actual: T): any;
failure(message: string): any;
}
```