npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rhizo

v1.2.0

Published

a framework-agnostic runner for composable test fixtures

Downloads

4

Readme

Rhizo

pipeline status

Rhizo is a simple runner for composable test fixtures you write. If you have tests which work with external concerns, especially databases, you're familiar with setting up fixtures in order to test processes which expect existing data. The simplest way to accommodate these tests is to tailor a fixture method to each functional area, but this quickly becomes impractical to maintain. By breaking down fixtures into small reusable components, you can build up the state each test requires without resorting to unsustainable copying and pasting.

Rhizo is framework-agnostic and will work with any test framework which allows you to set up hooks which run before a test or suite.

For more theoretical background, see this post. Rhizo implements the technique described there with a few differences to make it easier to apply as a standalone dependency.

Installation

npm i rhizo

Defining Fixtures

An ideal test fixture manages one and only one source datum (or small related collection thereof), and is composed with other single-responsibility fixtures to build the final state.

If, for example, you're writing tests for a hotel reservation system, you might have a fixture that inserts a guest, a second which inserts some rooms, and a third which adds a reservation. Your tests which concern guest profiles need only set up a stateFactory with the guests fixture, while tests evaluating calendar functionality require the reservations fixture to run after the other two -- and use the data they generate.

A fixture is an async or Promise-returning method, taking an environment which provides database connections and other such utilities, and a state which the fixture may read and write to. The state is passed in sequence from fixture to fixture.

Fixtures can be collected in an object but are best removed to a module or even a set of modules, ensuring easy access from all project test suites. The key corresponding to a fixture method will be written to in the state as the method executes: the guests fixture below will place the return value of the insert call in state.guests when executed, and so on.

exports = module.exports = {
  guests: (environment, state) => {
    return environment.db.guests.insert([{
      name: 'Jan Smith'
    }]);
  },
  rooms: (environment, state) => {
    return environment.db.rooms.insert([{
      number: 101,
      smoking: false
    }, {
      number: 102,
      smoking: false
    }]);
  },
  reservations: (environment, state) => {
    return environment.db.reservations.insert([{
      guest_id: state.guests[0].id,
      room_id: state.rooms[0].id,
      checkin_at: new Date('7/7/2018'),
      checkout_at: new Date('7/10/2018')
    }])
  }
};

A slightly neater organizational strategy involves breaking each fixture out into a module which exports the fixture method. The fixtures can be aggregated by dynamic requires:

const glob = require('glob');

exports.fixtures = glob.sync('test/helpers/fixtures/*.js').reduce((fixtures, file) => {
  fixtures[path.basename(file, '.js')] = require(path.resolve(file));

  return fixtures;
}, {});

The StateFactory

Invoke rhizo in a pre-test hook with the fixtures collection.

describe('a test suite', async function () {
  let stateFactory;
  let state;

  before(async function () {
    stateFactory = await rhizo(fixtures);
  });
});

The stateFactory Rhizo returns is a function taking two or more arguments. The first is an environment allowing you to pass in database connections and other static resources for use by the fixtures. All subsequent arguments are fixtures to be run in sequence. Each fixture is passed the environment and a state object ({} by default), which latter it modifies and passes on to the next fixture, building the completed state bit by bit.

The fixtures passed to the stateFactory are keys in the fixtures collection, but you can also inline fixture functions as shown below. These are identical to other fixtures in that they're async or Promise-returning functions which take an environment and state just like other fixtures. However, since they do not have names, they must explicitly modify and return the state.

  beforeEach(async function () {
    state = await stateFactory({
        db: db
      },
      'guests',
      'rooms',
      'reservations',
      async function adHocFixture(environment, state) {
        state.thing = 'stuff';

        return state;
      }
    );

    assert.lengthOf(state.guests, 1);
    assert.lengthOf(state.rooms, 2);
    assert.lengthOf(state.reservations, 1);
  });

state (and any database or other external changes behind it) can now be used in your tests.

Overriding and Passing States

The initial state is an empty object by default, but the environment may specify a state property of its own. If present, environment.state overrides the default. This allows states to be passed through multiple stateFactories, by setting the environment.state of the second invocation to the state output by the first. This is useful with nested describes with Mocha, for example: the outer beforeEach runs A, B, and C fixtures; then an inner beforeEach can leverage the work of the outer to run the D and E fixtures against the ABC state.

describe('outer', function () {
  let outerState;

  beforeEach(async function () {
    outerState = await stateFactory({
        db: db
      },
      'guests',
      'rooms'
    );
  });

  describe('inner', function () {
    beforeEach(async function () {
      state = await stateFactory({
          state: outerState,
          db: db
        },
        'reservations'
      );
    });
  });
});