Skip to content

Latest commit

 

History

History
344 lines (257 loc) · 10.6 KB

README.md

File metadata and controls

344 lines (257 loc) · 10.6 KB

natr

npm Build Status Coverage Status Commitizen friendly code style: prettier

Not another test runner! (natr) But it is, and it is a highly opinionated one that is used for own projects. In case you want to use it as well, you are very welcome to do and give feedback 😎

Important note: This runner is an MVP. Therefore, it is not stable, features missing and not every edge case tested, overall alpha software. Use at own risk. If you want to help get natr on track please read the contribution guide.

Contents

Click to show

Principles

The goal of natr is a reduced API by only using deep equal to check values for tests. This got inspired by RITEWay and uses the same API. Another goal was readability. Every test case should state a clear goal without over complicating things. By implement TAP it is also possible to already use a wide variety of tools together with natr for test coverage, color output and formatting. Speed is another factor. Tests should be executed often and therefore natr aims to be as fast as possible.

Installation

# NPM
npm install @krieselreihe/natr --save-dev

# Yarn
yarn add @krieselreihe/natr --dev

# PNPM
pnpm install @krieselreihe/natr --save-dev

Usage

Basic test

Unit tests are explicit, so there are no global variables injected. Import describe from the test runner to create a test suite:

import { describe } from "@krieselreihe/natr";

describe("my test suite", (assert) => {
  assert({
    given: "a basic arithmetic expression",
    should: "calculate 2",
    actual: 1 + 1,
    expected: 2,
  });
});

Now you can either execute it with natr by running the following command to execute all your test files:

natr "src/__tests__/*.test.js"

Or you can also use node to execute a single file. Every natr test suite is a standalone executable Javascript file:

node "src/__tests__/my.test.js"

Encapsulate execution

The execute helper gives you the chance to wrap an executions of multiple expressions to evaluate results. One example could be to retrieve the evaluated result of a promise:

import { describe, execute } from "@krieselreihe/natr";

describe("my test suite", async (assert) => {
  assert({
    given: "a promise",
    should: "resolve",
    actual: await execute(() => Promise.resolve(23)),
    expected: 23,
  });
});

Exceptions

To test if a function throws an error you can use the execute helper and pass a function to wrap your execution.

import { describe, execute } from "@krieselreihe/natr";

describe("my test suite", (assert) => {
  assert({
    given: "an expression that will throw",
    should: "throw an error",
    actual: execute(() => throw new Error("Doh!")),
    expected: new Error("Doh!"),
  });
});

React components

To render react component you can just use react-test-renderer and its API.

import { describe } from "@krieselreihe/natr";
import render from "react-test-renderer";

const MyComponent = () => (
  <div>
    <SubComponent foo="bar" />
  </div>
);
const SubComponent = () => <p className="sub">Sub</p>;

describe("my test suite", (assert) => {
  assert({
    given: "a react component",
    should: "be of a specific type",
    actual: render.create(<MyComponent />).findByType(SubComponent).type,
    expected: "SubComponent",
  });

  assert({
    given: "a react component",
    should: "have a specific prop",
    actual: render.create(<MyComponent />).findByType(SubComponent).props.foo,
    expected: "bar",
  });
});

Snapshot testing

Snapshot testing is included and can be used to test React component as JSON tree or regular JavaScript objects as well. The "expected" helper function toMatchSnapshot is exposed, that will create a snapshot on first run and match against on further.

import { describe, toMatchSnapshot } from "@krieselreihe/natr";
import render from "react-test-renderer";

const MyComponent = () => (
  <div>
    <SubComponent foo="bar" />
  </div>
);
const SubComponent = () => <p className="sub">Sub</p>;

describe("my test suite", (assert) => {
  assert({
    given: "a react component",
    should: "match snapshot",
    actual: render.create(<MyComponent />).toJSON(),
    expected: toMatchSnapshot(),
  });

  assert({
    given: "JavaScript object",
    should: "match snapshot",
    actual: { foo: 42 },
    expected: toMatchSnapshot(),
  });
});

Snapshots will be saved next to the test file in a folder called __snapshots__ as JSON files. To regenerate snapshots you can either delete the snapshot file or use the --updateSnapshot / -u flag provided by the runner CLI:

natr -u

CLI

Basic usage

After installing natr you can use the CLI to execute multiple test files at once. To use glob patterns provided by natr the pattern should be in quotes:

natr "src/**/__tests__/*.(js|jsx)"

It uses fast-glob, you can find documentation about the supported patterns at the fast-glob pattern syntax documentation. If you do not want to use fast-glob for "pattern matching" you can write patterns without quotes to let your shell handle the file matching:

natr src/__tests__/*.js

Note: It is always possible to just use node to execute your tests. There are no magic global variables defined.

# Execute a single test case with node
node src/__tests__/my.test.js

Preload files and modules

To preload modules and files you can use the --require (-r) flag for the CLI. It can also be used multiple times if needed. For example, you can require Babel to enable certain transformations like supporting JSX or new ECMAScript features (for Babel you need also a .babelrc configuration file to make it work).

# Register babel
natr "src/__tests__/*.test.(js|jsx)" -r @babel/register
# Preload a file
natr "src/__tests__/*.test.(js|jsx)" -r ./tests/setup.js

Update snapshot

To update your generated snapshot tests you can add the --updateSnapshot (-u) flag to the CLI.

natr -u "src/__tests__/*.test.(js|jsx)"

Coverage reports

To generate code coverage reports you can combine istanbul with tap-nyc.

nyc --reporter=text natr "src/__tests__/*.test.js" | tap-nyc

You can also integrate this with coveralls by using node-coveralls and the text-lcov reporter of istanbul:

nyc --reporter=text-lcov natr "src/__tests__/*.test.js" | coveralls

Format output

Through the implementation of TAP you can use a variety of formatter that already exist. As long as they take TAP formatted text as input you can use it. Here is a list of some of them:

Probably many, many more. To use any of them install it and pipe the natr output to the respective formatter:

natr "src/__tests__/*.test.js" | tap-nirvana

API

Described as TypeScript the API would like the following:

interface Assert<TestType = any> {
  given: string;
  should: string;
  actual: TestType;
  expected: TestType;
}

type UnitTest = (assert: Assert) => void;

type TestSuite = (test: UnitTest) => Promise<void> | void;

type Describe = (unit: string, fn: TestSuite) => void;

type Execute<ReturnType = any> = (
  callback: () => ReturnType,
) => Promise<ReturnType> | ReturnType;

describe

Describes a test suite:

describe(string, TestSuite);

assert

Gets passed by describes test suite function and describes a single unit test with an object:

describe(string, [async] assert => {
  assert({
    given: string,
    should: string,
    actual: any,
    expected: any
  });
});

execute

Function to execute code to either a returned value or a thrown error.

assert({
  given: string,
  should: string,
  actual: [await] execute(() => any),
  expected: any
});

Disclaimer

This runner was highly inspired by RITEWay on how to write tests with focus on simplicity (e.g. only use deep equal, enforce reduced API), and node-tap on how to log test reports based on the TAP (Test Anything Protocol). Also, Jest for snapshot testing and tape on actual creating a runner that had to hold as base for natr.