Research
Security News
Threat Actor Exposes Playbook for Exploiting npm to Build Blockchain-Powered Botnets
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
A library for setting up JavaScript factories to help build objects as test data, with full TypeScript support
Fishery is a JavaScript library for creating test data. It allows you to define factories for your data models, making it easy to generate consistent and realistic test data for your applications.
Defining a Factory
This feature allows you to define a factory for a data model. In this example, a factory for a user model is defined with an id, name, and email. The `sequence` function ensures that each generated user has a unique id and email.
const { Factory } = require('fishery');
const userFactory = Factory.define(({ sequence }) => ({
id: sequence,
name: `User ${sequence}`,
email: `user${sequence}@example.com`
}));
const user = userFactory.build();
console.log(user);
Building Multiple Instances
This feature allows you to generate multiple instances of a model. In this example, three user instances are generated using the `buildList` method.
const users = userFactory.buildList(3);
console.log(users);
Customizing Instances
This feature allows you to customize the generated instances. In this example, a user instance is generated with a custom name while other attributes are generated by the factory.
const customUser = userFactory.build({ name: 'Custom User' });
console.log(customUser);
Associations
This feature allows you to define associations between different models. In this example, a post model is defined with an associated user model as the author.
const postFactory = Factory.define(({ sequence }) => ({
id: sequence,
title: `Post ${sequence}`,
author: userFactory.build()
}));
const post = postFactory.build();
console.log(post);
Faker is a popular library for generating fake data. It provides a wide range of data types and formats, making it versatile for various use cases. Unlike Fishery, Faker does not focus on defining factories for data models but rather on generating random data directly.
Factory-girl is a library for defining and building factories for JavaScript objects. It is similar to Fishery in that it allows you to define factories for your data models. However, Fishery offers a more modern API and better TypeScript support.
Rosie is another factory library for JavaScript. It allows you to define factories and build objects with complex relationships. Compared to Fishery, Rosie has a more verbose API and less intuitive syntax.
Fishery is a library for setting up JavaScript objects for use in tests, Storybook, and anywhere else you need to set up data. It is loosely modeled after the Ruby gem, factory_bot.
Fishery is built with TypeScript in mind. Factories accept typed parameters and return typed objects, so you can be confident that the data used in your tests is valid. If you aren't using TypeScript, that's fine too – Fishery still works, just without the extra typechecking that comes with TypeScript.
Install fishery with:
npm install --save fishery
or
yarn add fishery
A factory is just a function that returns your object. Fishery provides
several arguments to your factory function to help with common situations.
After defining your factory, you can then call build()
on it to build your
objects. Here's how it's done:
// factories/user.ts
import { Factory } from 'fishery';
import { User } from '../my-types';
export default Factory.define<User>(({ sequence, factories }) => ({
id: sequence,
name: 'Bob',
address: { city: 'Grand Rapids', state: 'MI', country: 'USA' },
posts: factories.post.buildList(2),
}));
Now combine your factories together and register them:
// factories/index.ts
import { register } from 'fishery';
import user from './user';
import post from './post';
export const factories = register({
user,
post,
});
Pass parameters as the first argument to build
to override your factory
defaults. These parameters are deep-merged into the default object returned by
your factory.
build
also supports a seconds argument with the following keys:
transient
: data for use in your factory that doesn't get overlaid onto your
result object. More on this in the Transient
Params sectionassociations
: often not required but can be useful in the case of
bi-directional associations. More on this in the Associations
section// my-test.test.ts
import { factories } from './factories';
const user = factories.user.build({
name: 'Susan',
address: { city: 'Detroit' },
});
user.name; // Susan
user.address.city; // Detroit
user.address.state; // MI (from factory)
Factories are fully typed, both when defining your factories and when using them to build objects, so you can be confident the data you are working with is correct.
const user = factories.user.build();
user.foo; // type error! Property 'foo' does not exist on type 'User'
const user = factories.user.build({ foo: 'bar' }); // type error! Argument of type '{ foo: string; }' is not assignable to parameter of type 'Partial<User>'.
export default Factory.define<User, Factories, UserTransientParams>(
({ sequence, params, transientParams, associations, afterCreate }) => {
params.firstName; // Property 'firstName' does not exist on type 'DeepPartial<User>
transientParams.foo; // Property 'foo' does not exist on type 'Partial<UserTransientParams>'
associations.bar; // Property 'bar' does not exist on type 'Partial<User>'
afterCreate(user => {
user.foo; // Property 'foo' does not exist on type 'User'
});
return {
id: `user-${sequence}`,
name: 'Bob',
post: null,
};
},
);
If your factory references another factory, use the factories
object
provided to the factory:
const postFactory = Factory.define<Post, Factories>(({ factories }) => ({
title: 'My Blog Post',
author: factories.user.build(),
}));
If you'd like to be able to pass in an association when building your object and
short-circuit the call to factories.xxx.build()
, use the associations
variable provided to your factory:
const postFactory = Factory.define<Post, Factories>(
({ factories, associations }) => ({
title: 'My Blog Post',
author: associations.author || factories.user.build(),
}),
);
Then build your object like this:
factories.post.build({}, { associations: { author: susan } });
factories
factory argumentIn the above examples, the Factories
generic parameter is passed to
define
. This is optional but recommended in order to get type-checking of
the factories
object. You can define your Factories
type like this:
// factories/types.ts
export interface Factories {
user: Factory<User>;
post: Factory<Post>;
}
Once you've defined your Factories
type, it can also be used when
registering your factories. This ensures that your Factories
type is always
in sync with the actual factories that you have registered:
// factories/index.ts
import { register } from 'fishery';
import user from './user';
import post from './post';
import { Factories } from './types';
export const factories: Factories = register({ user, post });
params
to access passed in propertiesThe parameters passed in to build
are automatically overlaid on top of the
default properties defined by your factory, so it is often not necessary to
explicitly access the params in your factory. This can, however, be useful,
for example, if your factory uses the params to compute other properties:
const userFactory = Factory.define<User, Factories>(({ params }) => {
const { name = 'Bob Smith' } = params;
const email = params.email || `${kebabCase(name)}@example.com`;
return {
name,
email,
posts: [],
};
});
Factories can accept parameters that are not part of the resulting object. We call these transient params. When building an object, pass any transient params in the second argument:
const user = factories.user.build({}, { transient: { registered: true } });
Transient params are passed in to your factory and can then be used however you like:
interface User {
name: string;
posts: Post[];
memberId: string | null;
permissions: { canPost: boolean };
}
interface UserTransientParams {
registered: boolean;
numPosts: number;
}
const userFactory = Factory.define<User, Factories, UserTransientParams>(
({ transientParams, factories, sequence }) => {
const { registered, numPosts = 1 } = transientParams;
const user = {
name: 'Susan Velasquez',
posts: factories.posts.buildList(numPosts),
memberId: registered ? `member-${sequence}` : null,
permissions: {
canPost: registered,
},
};
},
);
In the example above, we also created a type called UserTransientParams
and
passed it as the third generic type to define
. This isn't required but
gives you type checking of transient params, both in the factory and when
calling build
.
When constructing objects, any regular params you pass to build
take
precedence over the transient params:
const user = factories.user.build(
{ memberId: '1' },
{ transient: { registered: true } },
);
user.memberId; // '1'
user.permissions.canPost; // true
Passing transient params to build
can be a bit verbose. It is often a good
idea to consider creating a reusable builder method instead of or in
addition to your transient params to make building objects simpler.
You can instruct factories to execute some code after an object is created. This can be useful if a reference to the object is needed, like when setting up relationships:
export default Factory.define<User, Factories>(
({ factories, sequence, afterCreate }) => {
afterCreate(user => {
const post = factories.post.build({}, { associations: { author: user } });
user.posts.push(post);
});
return {
id: sequence,
name: 'Bob',
posts: [],
};
},
);
Factories can easily be extended using the extension methods: params
,
transient
, associations
, and afterCreate
. These set default attributes that get passed to the factory on build
:
const userFactory = Factory.define<User>(() => ({
name: 'Kassandra',
admin: false,
}));
const adminFactory = userFactory.params({ admin: true });
const admin = adminFactory.build();
admin.admin; // true
Factories are immutable, so the extension methods return a new factory with
the specified params
, transientParams
, associations
, or afterCreate
added to it. When build
is called on the factory, the params
,
transientParams
, and associations
are passed in along with the values
supplied to build
. Values supplied to build
override these defaults.
afterCreate
just adds a function that is called when the object is built.
The afterCreate
defined in Factory.define
is always called first if
specified, and then any afterCreate
functions defined with the extension
method are called sequentially in the order they were added.
These extension methods can be called multiple times to continue extending factories, and they do not modify the original factory:
const eliFactory = userFactory
.params({ admin: true })
.params({ name: 'Eli' })
.afterCreate(user => console.log('hello'))
.afterCreate(user => console.log('there'));
const user = eliFactory.build();
// log: hello
// log: there
user.name; // Eli
user.admin; // true
const user2 = eliFactory.build({ admin: false });
user.name; // Eli
user2.admin; // false
If you find yourself frequently building objects with a certain set of properties, it might be time to either extend the factory or create a reusable builder method.
Factories are just classes, so adding reusable builder methods is as simple
as subclassing Factory
and defining any desired methods:
class UserFactory extends Factory<User, Factories, UserTransientParams> {
admin(adminId: string) {
return this.params({
admin: true,
adminId: adminId || `admin-${this.sequence()}`,
});
}
registered() {
return this
.params({ memberId: this.sequence() })
.transient({ registered: true })
.associations({ profile: factories.profile.build() })
.afterCreate(user => console.log(user))
}
}
// instead of Factory.define<User>
const userFactory = UserFactory.define(() => ({ ... }))
const user = userFactory.admin().registered().build()
To learn more about the factory builder methods params
, transient
,
associations
, and afterCreate
, see Extending factories, above.
register
Factories should usually be defined and then combined together using register
:
// factories/index.ts
import { register } from 'fishery';
import user from './user';
import post from './post';
import { Factories } from './types';
export const factories: Factories = register({ user, post });
The factories passed to register get injected into each factory so factories can access each other. This prevents circular dependencies that could arise if your factories try to access other factories directly by importing them and also creates a convenient way for factories to access other factories without having to explicitly import them.
If you are defining a factory for use in a single test file, you might not wish
to register the factory or use the factories
object that gets injected to the
factory. In this case, you can use defineUnregistered
instead of define
and
then skip calling register
, eg:
const personFactory = Factory.defineUnregistered<Person>(() => ({
name: 'Sasha',
}));
const person = personFactory.build();
See the CONTRIBUTING document. Thank you, contributors!
This project name was inspired by Patrick Rothfuss' Kingkiller Chronicles books. In the books, the artificery, or workshop, is called the Fishery for short. The Fishery is where things are built.
Fishery is Copyright © 2020 Stephen Hanson and thoughtbot. It is free software, and may be redistributed under the terms specified in the LICENSE file.
Fishery is maintained and funded by thoughtbot, inc. The names and logos for thoughtbot are trademarks of thoughtbot, inc.
We love open source software! See our other projects or hire us to design, develop, and grow your product.
FAQs
A library for setting up JavaScript factories to help build objects as test data, with full TypeScript support
The npm package fishery receives a total of 201,617 weekly downloads. As such, fishery popularity was classified as popular.
We found that fishery demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
Security News
NVD’s backlog surpasses 20,000 CVEs as analysis slows and NIST announces new system updates to address ongoing delays.
Security News
Research
A malicious npm package disguised as a WhatsApp client is exploiting authentication flows with a remote kill switch to exfiltrate data and destroy files.