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.
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
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;
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;
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.
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();
});
});
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);
});
});
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');
});
});
It's also possible to use one of the hooks that Factory Builder provides for injecting some code;
beforeBuild
- called before building the factoryafterBuild
- 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
andafterBuild
to either thebuild
orbuildList
method. These hooks should be defined on theFactory
itself.
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' };
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 thebeforeBuild
andafterBuild
hooks for the factory.
You could use a third party library like fakerjs to create fake data for your factories or just define it yourself.
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');
Looking to contribute? Look for the Good First Issue label.
Please file an issue for bugs, missing documentation, or unexpected behavior.
Please file an issue to suggest new features. Vote on feature requests by adding a 👍. This helps maintainers prioritize what to work on.
For questions related to using the library, please file an issue on GitHub with the Question label.
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.