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

factory-builder

v3.0.5

Published

An easy way to create framework agnostic scalable test factories

Downloads

135

Readme

Factory Builder

GitHub license NPM Version downloads Test package PRs Welcome

Factory Builder is a framework agnostic and scalable fixtures replacement for your test suite. It has a straightforward definition syntax, mimics multiple build strategies (unsaved instances and attribute hashes), and it allows you to create multiple factories (variants) for the same instance.

It's heavily inspired by FactoryBot by Thoughtbot.

Table of Contents

Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:

// When using NPM
npm install --save-dev factory-builder

// Or when using Yarn
yarn add --dev factory-builder

Examples

Defining factories

Each factory has a name and a set of attributes. It is highly recommended that you have one factory for each class that provides the simplest set of attributes necessary to create an instance of that class.

Note that the factory is an Object with the attributes key. The value of this attributes key can either be a function that returns an Object or an Object.

// ./factories/User.js
const User = {
  attributes: {
    firstName: 'Thom',
    lastName: 'Taylor',
    email: '[email protected]',
  }
}

export default User;

Lazily evaluate the attributes

Most of the time, defining the attributes as a object on the factory will be enough. However, the attributes can also be defined as a function. This is useful for when one wants to assign a dynamic value to any of the attributes.

// ./factories/User.js
import { v4 as uuid } from 'uuid'; // NOTE: The `uuid` library is not part of Factory Builder.

const User = {
  attributes: () => ({
    id: uuid(),
    firstName: 'Thom',
    lastName: 'Taylor',
    email: '[email protected]',
  })
}

export default User;

Consuming factories

On it's own there is nothing special about factories. They're just plain javascript object that return a specifically styled object. To actually use the factory in your test you can make use of multiple methods that are provided by Factory Buider;

  • build: builds a single, new factory.
  • buildList: builds a given number of factories.
  • attributesFor: returns the attributes of a factory.

Building a new factory with build.

Factory Builder doesn't depend on any database, which makes it easy to quickly run your entire test suite. The build simple creates a new factory.

// ./specs/User.js
import { build } from 'factory-builder';
import User from './factories/User';

describe('User', () => {
  it('builds a new user', () => {
    const user = build(User, { lastName: 'build' });
    expect(user.lastName).toEqual('build');
    expect(user.id).toBeUndefined();
  });
});

Building multiple factories with buildList.

You can also create multiple factories at the same time. For this you can use the buildList functions that the Factory Builder package provides.

// ./specs/User.js
import { buildList } from 'factory-builder';
import User from './factories/User';

describe('User', () => {
  it('builds 2 new users', () => {
    const users = buildList(User, 2, { lastName: 'build' });
    expect(users.length).toEqual(2);
  });
});

Building multiple factories with different attributes

In addition to passing an object with attributes, it's also possible to pass an array to buildList in order to use different values for multiple factories.

// ./specs/User.js
import { buildList } from 'factory-builder';
import User from './factories/User';

describe('User', () => {
  it('builds 2 new users with different values', () => {
    const users = buildList(User, 2, [{ lastName: 'build' }, { lastName: 'something' }]);
    expect(users.length).toEqual(2);
    expect(users[0].lastName).toEqual('build');
    expect(users[1].lastName).toEqual('something');
  });
});

Before and after hooks

It's also possible to use one of the hooks that Factory Builder provides for injecting some code;

  • beforeBuild - called before building the factory
  • afterBuild - called after building the factory

These methods can be globally defined on your factory or can be passed to the build method. These hooks enable you to modify or use create data to do whatever you want. The before* and after* methods should always return an Object and they always have one argument: the attributes of the factory.

// ./factories/User.js
const User = {
  attributes: {
    firstName: 'Peter',
  },

  beforeBuild: (attributes) => {
    return {
      ...attributes,
      lastName: `${attributes.firstName}s`,
    };
  },

  afterBuild: (attributes) => {
    return {
      ...attributes,
      email: `${attributes.firstName}@email.org`,
    };
  },
};

export default User;

NOTE: It's currently not possible to pass the beforeBuild and afterBuild to either the build or buildList method. These hooks should be defined on the Factory itself.

Using variants

Variants enable you to define multiple variants of the same base factory. You can use the as key when you create or build a factory and it will return the variant you've defined.

const User = {
  attributes: {
    firstName: 'Peter',
    isClient: false,
    isAdmin: false,
  },
  variants: {
    admin: {
      isClient: false,
      isAdmin: true,
    },
    client: {
      isClient: true,
    }
  }
};

import { build } from 'factory-builder';
build(User, as: 'admin'); // => { firstName: 'Peter', isClient: false, isAdmin: true };

NOTE: The variant must be a plain object and should be namespaced in your factory under variants. The key name will also be the way you pick this specific factory variant.

The attributes of a specific variant can also be lazily evaluated. Just like the regular attributes of a Factory. To make use of the lazy evaluation, use a function that returns an object.

import { v4 as uuid } from 'uuid'; // NOTE: The `uuid` library is not part of Factory Builder.

const User = {
  attributes: {
    firstName: 'Peter',
  },

  variants: {
    created: () => ({ id: uuid() })
  }
};

import { build } from 'factory-builder';
build(User, as: 'created'); // => { firstName: 'Peter', id: '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' };

Quickly grab the attributes with attributesFor.

Want to quickly get all the attributes for a factory (for example to use it as POST data in your tests), you can use the attributesFor method. This will return an object with all the attributes of a factory.

// ./specs/User.js
import { attributesFor } from 'factory-builder';
import User from './factories/User';

describe('User', () => {
  it('sends an API request', () => {
    const userData = attributesFor(User, { lastName: 'Moses' });

    const response = FakeApi('/some-url', {
      method: 'POST',
      body: JSON.stringify(userData),
    });

    expect(response.lastName).toEqual('Moses');
  });
});

NOTE: The attributesFor doesn't call the beforeBuild and afterBuild hooks for the factory.

Generating data (optional)

You could use a third party library like fakerjs to create fake data for your factories or just define it yourself.

Using with a ORM

If you want to persist your factory built objects in order to test the actual creation in a database, it's possible with afterBuild. Here is an example with Sequelize where db has been defined globally for test purposes. We're calling Sequelize's create function with the object attributes in the afterBuild method.

const User = {
  attributes: {
    firstName: 'Thom',
    lastName: 'Taylor',
    email: '[email protected]',
  },

  afterBuild: async (attributes) => global.db.user.create(attributes),
};

Build the factory and await the created database entity and test it.

const user = await build(User);
expect(user.firstName).toEqual('Thom');

Issues

Looking to contribute? Look for the Good First Issue label.

🐛 Bugs

Please file an issue for bugs, missing documentation, or unexpected behavior.

See Bugs

💡 Feature Requests

Please file an issue to suggest new features. Vote on feature requests by adding a 👍. This helps maintainers prioritize what to work on.

See Feature Requests

❓ Questions

For questions related to using the library, please file an issue on GitHub with the Question label.

🛟 Supported NodeJS Versions

NodeJS version 14.x and up are supported. Older versions of NodeJS aren't explitly supported as they're not part of our CI setup. If you find anything, please let us know.