npm.io
0.2.2 • Published yesterday

jeta

Licence
ISC
Version
0.2.2
Deps
0
Size
15 kB
Vulns
0
Weekly
0

Jeta

Jeta is the assertion and testing package for Jetjs. It provides a lightweight testing toolkit with assertions, suites, function spies, and CommonJS module mocking.

Jeta is part of the Jetjs ecosystem, but it can also be installed and used independently in CommonJS Node.js projects.

Contents

Installation

Install jeta as a development dependency:

npm install --save-dev jeta

Jeta uses CommonJS modules.

Quick start

Create a test file:

// calculator.test.js
const {describe, expect, result, test} = require('jeta');

function add(a, b) {
  return a + b;
}

describe('Calculator', () => {
  test('adds two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });

  test('returns a number', () => {
    expect(add(2, 3)).toBe(expect.any(Number));
  });
});

process.exit(result());

Add a test script to package.json:

{
  "scripts": {
    "test": "node calculator.test.js"
  }
}

Run the tests:

npm test

Jeta prints each test result and exits with status 0 when all tests pass or status 1 when one or more tests fail.

Multiple test files

Tests can be colocated with the files they cover:

project/
├── src/
│   ├── calculator.js
│   ├── calculator.test.js
│   ├── formatter.js
│   └── formatter.test.js
├── test.js
└── package.json

Each test file defines its own suites and tests:

// src/calculator.test.js
const {describe, expect, test} = require('jeta');
const {add} = require('./calculator');

describe('Calculator', () => {
  test('adds two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });
});

Use a central runner to load every test file explicitly, then report the result:

// test.js
const {result} = require('jeta');

require('./src/calculator.test');
require('./src/formatter.test');

process.exit(result());
{
  "scripts": {
    "test": "node test.js"
  }
}

Jeta does not automatically discover test files. Explicitly requiring them keeps the test setup predictable and dependency-free.

Using Jeta with Jetjs

When Jeta is used as part of the Jetjs framework, jetc can run a test entry point that discovers and evaluates the files in src.

If the project uses JSX and the jete package is available, Jeta also exposes render for rendering UI components as HTML strings in tests:

import {describe, expect, render, test} from 'jeta';
import App from './app';

describe('App routes', () => {
  test('Renders the home route', () => {
    expect(render(<App />)).toInclude('Home');
  });
});

render delegates to jete.renderToString. It is optional: projects that do not use JSX can use Jeta without installing jete, but calling render without jete installed will throw an error.

Create test.js:

import path from 'path';
import {discover, evaluate, result} from 'jeta';

export default () => {
  discover(path.join(process.cwd(), 'src'))
      .then(evaluate)
      .then(() => process.exit(result()))
      .catch((error) => console.error(error));
};

Add the test script to package.json:

{
  "scripts": {
    "test": "jetc test.js"
  }
}

Run the tests:

npm test

Assertions

assert(condition)

Passes when the condition is truthy and fails when it is falsy.

const {assert} = require('jeta');

assert(2 + 2 === 4);

Use assert.not to assert the opposite:

assert.not(false);
assert.not(2 + 2 === 5);
expect(received).toBe(expected)

Compares the received and expected values. Plain objects and arrays are compared recursively.

expect(42).toBe(42);
expect({name: 'Jeta'}).toBe({name: 'Jeta'});
expect([1, {active: true}]).toBe([1, {active: true}]);
expect(NaN).toBe(NaN);

Negate an expectation with .not:

expect(42).not.toBe(10);
expect({active: true}).not.toBe({active: false});
expect(received).toInclude(expected)

Checks whether a string contains a substring or an array contains an equal value.

expect('Welcome to Jetjs').toInclude('Jetjs');
expect(['assert', 'expect', 'test']).toInclude('expect');
expect([{id: 1}, {id: 2}]).toInclude({id: 2});

Array values use the same recursive comparison as toBe:

expect([{name: 'Jeta'}]).toInclude({name: 'Jeta'});

String matching requires a string value and does not coerce other types:

expect('version 2').toInclude('2');
expect('version 2').not.toInclude(2);

Negation is supported:

expect('Jeta').not.toInclude('Java');
expect([1, 2, 3]).not.toInclude(4);
expect.any(type)

Matches any value with the expected JavaScript typeof result. Pass a constructor such as Boolean, Number, or String, or pass a type name such as 'function'. It can be used with toBe, inside nested values, and with array inclusion.

expect(true).toBe(expect.any(Boolean));
expect('Jeta').toBe(expect.any(String));
expect({id: 12}).toBe({id: expect.any(Number)});
expect([false, 10]).toInclude(expect.any(Boolean));

Call expect.any() without a type to match any value:

expect('anything').toBe(expect.any());
expect.re(pattern)

Creates a RegExp from the supplied pattern and tests the received value with it:

expect('Welcome to Jeta').toBe(expect.re('Jeta'));
expect('release-42').toBe(expect.re('release-\d+'));

It can also match values contained in an array:

expect(['alpha', 'release-42']).toInclude(expect.re('release-\d+'));

Suites and tests

describe(title, suite)

Groups related tests under a title:

describe('User', () => {
  test('has a name', () => {
    expect({name: 'John'}).toBe({name: 'John'});
  });
});
test(description, condition)

Runs a synchronous test condition and records whether it passed or failed. An assertion failure or any other thrown error fails the test.

Jeta does not await promises. Complete asynchronous work before making assertions or finishing the test callback.

test('adds numbers', () => {
  expect(1 + 2).toBe(3);
});
result()

Prints the total number of suites, passed tests, and failed tests. It returns:

  • 0 when no tests failed, including when no tests ran
  • 1 when at least one test failed

Call it after all test files have loaded:

const {result} = require('jeta');

require('./src/calculator.test');
require('./src/formatter.test');

process.exit(result());

Function spies

fn(implementation)

Creates a spy function. An optional implementation controls its behavior:

const {expect, fn} = require('jeta');

const add = fn((a, b) => a + b);

expect(add(2, 3)).toBe(5);
add.hasBeenCalled(1);
add.hasBeenCalledWithArgs(2, 3);

Without an implementation, the spy returns undefined:

const callback = fn();

callback('complete');
callback.hasBeenCalled();
callback.hasBeenCalledWithArgs('complete');

hasBeenCalled() passes after one or more calls. Supply a number to require an exact call count:

const callback = fn();

callback();
callback();
callback.hasBeenCalled(2);

hasBeenCalledWithArgs() compares the arguments from the most recent call.

Module mocking

mock(path, exportName, implementation)

Replaces a named CommonJS export with a spy. It is intended for exported functions.

Create the mock before requiring the module and any modules that depend on it. References captured before mock() is called cannot be replaced.

const {expect, mock} = require('jeta');

const requestMock = mock('./request', 'get', () => ({status: 200}));
const request = require('./request');

expect(request.get('/users')).toBe({status: 200});
requestMock.hasBeenCalledWithArgs('/users');

requestMock.restore();

Always call .restore() when the test no longer needs the mock:

test('loads a user', () => {
  const get = mock('./request', 'get', () => ({id: 1}));

  try {
    const {loadUser} = require('./user');
    expect(loadUser()).toBe({id: 1});
    get.hasBeenCalled();
  } finally {
    get.restore();
  }
});

API

Jeta exports:

const {
  assert,
  describe,
  discover,
  evaluate,
  expect,
  fn,
  mock,
  render,
  result,
  test
} = require('jeta');

License

ISC