{"id":13727151,"url":"https://github.com/thoughtbot/fishery","last_synced_at":"2025-04-28T15:42:03.964Z","repository":{"id":40588246,"uuid":"165299704","full_name":"thoughtbot/fishery","owner":"thoughtbot","description":"A library for setting up JavaScript objects as test data","archived":false,"fork":false,"pushed_at":"2025-01-31T22:08:23.000Z","size":1060,"stargazers_count":909,"open_issues_count":18,"forks_count":36,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-04-13T16:08:26.651Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thoughtbot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"thoughtbot"}},"created_at":"2019-01-11T19:42:26.000Z","updated_at":"2025-04-11T00:12:14.000Z","dependencies_parsed_at":"2023-11-28T23:25:51.776Z","dependency_job_id":"d10a3f89-13e3-45f5-876b-3bee082099ed","html_url":"https://github.com/thoughtbot/fishery","commit_stats":{"total_commits":102,"total_committers":14,"mean_commits":7.285714285714286,"dds":0.2450980392156863,"last_synced_commit":"2bd552c8185bfce29c90faa09dd2a576e6282663"},"previous_names":[],"tags_count":18,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2Ffishery","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2Ffishery/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2Ffishery/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thoughtbot%2Ffishery/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thoughtbot","download_url":"https://codeload.github.com/thoughtbot/fishery/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248741206,"owners_count":21154255,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T01:03:41.750Z","updated_at":"2025-04-13T16:08:27.699Z","avatar_url":"https://github.com/thoughtbot.png","language":"TypeScript","funding_links":["https://github.com/sponsors/thoughtbot"],"categories":["TypeScript"],"sub_categories":[],"readme":"# Fishery\n\n[![CircleCI](https://circleci.com/gh/thoughtbot/fishery.svg?style=svg)](https://circleci.com/gh/thoughtbot/fishery)\n\nFishery is a library for setting up JavaScript objects for use in tests and\nanywhere else you need to set up data. It is loosely modeled after the Ruby\ngem, [factory_bot][factory_bot].\n\nFishery is built with TypeScript in mind. Factories accept typed parameters and\nreturn typed objects, so you can be confident that the data used in your tests\nis valid. If you aren't using TypeScript, that's fine too – Fishery still works,\njust without the extra typechecking that comes with TypeScript.\n\n## Installation\n\nInstall fishery with:\n\n```\nnpm install --save-dev fishery\n```\n\nor\n\n```\nyarn add --dev fishery\n```\n\n## Usage\n\nA factory is just a function that returns your object. Fishery provides\nseveral arguments to your factory function to help with common situations.\nAfter defining your factory, you can then call `build()` on it to build your\nobjects. Here's how it's done:\n\n### Define and use factories\n\n```typescript\n// factories/user.ts\nimport { Factory } from 'fishery';\nimport { User } from '../my-types';\nimport postFactory from './post';\n\nconst userFactory = Factory.define\u003cUser\u003e(({ sequence }) =\u003e ({\n  id: sequence,\n  name: 'Rosa',\n  address: { city: 'Austin', state: 'TX', country: 'USA' },\n  posts: postFactory.buildList(2),\n}));\n\nconst user = userFactory.build({\n  name: 'Susan',\n  address: { city: 'El Paso' },\n});\n\nuser.name; // Susan\nuser.address.city; // El Paso\nuser.address.state; // TX (from factory)\n```\n\n### Asynchronously create objects with your factories\n\nIn some cases, you might want to perform an asynchronous operation when building objects, such as saving an object to the database. This can be done by calling `create` instead of `build`. First, define an `onCreate` for your factory that specifies the behavior of `create`, then create objects with `create` in the same way you do with `build`:\n\n```typescript\nconst userFactory = Factory.define\u003cUser\u003e(({ onCreate }) =\u003e {\n  onCreate(user =\u003e User.create(user));\n\n  return {\n    ...\n  };\n});\n\nconst user = await userFactory.create({ name: 'Maria' });\nuser.name; // Maria\n```\n\n`create` returns a promise instead of the object itself but otherwise has the same API as `build`. The action that occurs when calling `create` is specified by defining an `onCreate` method on your factory as [described below](#on-create-hook).\n\n`create` can also return a different type from `build`. This type can be specified when defining your factory:\n\n```\nFactory.define\u003cReturnTypeOfBuild, TransientParamsType, ReturnTypeOfCreate\u003e\n```\n\n## Documentation\n\n### Typechecking\n\nFactories are fully typed, both when defining your factories and when using them\nto build objects, so you can be confident the data you are working with is\ncorrect.\n\n```typescript\nconst user = userFactory.build();\nuser.foo; // type error! Property 'foo' does not exist on type 'User'\n```\n\n```typescript\nconst user = userFactory.build({ foo: 'bar' }); // type error! Argument of type '{ foo: string; }' is not assignable to parameter of type 'Partial\u003cUser\u003e'.\n```\n\n```typescript\nconst userFactory = Factory.define\u003cUser, UserTransientParams\u003e(\n  ({\n    sequence,\n    params,\n    transientParams,\n    associations,\n    afterBuild,\n    onCreate,\n  }) =\u003e {\n    params.firstName; // Property 'firstName' does not exist on type 'DeepPartial\u003cUser\u003e\n    transientParams.foo; // Property 'foo' does not exist on type 'Partial\u003cUserTransientParams\u003e'\n    associations.bar; // Property 'bar' does not exist on type 'Partial\u003cUser\u003e'\n\n    afterBuild(user =\u003e {\n      user.foo; // Property 'foo' does not exist on type 'User'\n    });\n\n    return {\n      id: `user-${sequence}`,\n      name: 'Bob',\n      post: null,\n    };\n  },\n);\n```\n\n### `build` API\n\n`build` supports a second argument with the following keys:\n\n- `transient`: data for use in your factory that doesn't get overlaid onto your\n  result object. More on this in the [Transient\n  Params](#params-that-dont-map-to-the-result-object-transient-params) section\n- `associations`: often not required but can be useful in order to short-circuit creating associations. More on this in the [Associations](#Associations)\n  section\n\n### Use `params` to access passed in properties\n\nThe parameters passed in to `build` are automatically overlaid on top of the\ndefault properties defined by your factory, so it is often not necessary to\nexplicitly access the params in your factory. This can, however, be useful,\nfor example, if your factory uses the params to compute other properties:\n\n```typescript\nconst userFactory = Factory.define\u003cUser\u003e(({ params }) =\u003e {\n  const { name = 'Bob Smith' } = params;\n  const email = params.email || `${kebabCase(name)}@example.com`;\n\n  return {\n    name,\n    email,\n    posts: [],\n  };\n});\n```\n\n#### Typechecking params that are classes\n\nBy default Fishery uses a Typescript DeepPartial to make params and their properties optional. But this doesn't make sense when a param is a class instance. To override the DeepPartial params typechecking behavior, you can add a generic parameter to explain what the expected type of of the params is.\n\n```typescript\ntype EventParams = DeepPartial\u003cEvent\u003e \u0026 {\n  createdAt?: DateTime; // Overrides Event['createdAt'] that was DeepPartialObject\u003cDateTime\u003e\n};\n\nconst eventFactory = Factory.define\u003cEvent, {}, Event, EventParams\u003e(\n  ({ params }) =\u003e {\n    const createdAt = DateTime.fromISO(new Date().toISOString());\n    const updatedAt = params.createdAt ?? createdAt;\n\n    return {\n      id: '1',\n      name: '1:1 Pairing Session',\n      createdAt,\n      updatedAt,\n    };\n  },\n);\nconst event = eventFactory.build({ createdAt: new DateTime() });\n```\n\n### Params that don't map to the result object (transient params)\n\nFactories can accept parameters that are not part of the resulting object. We\ncall these transient params. When building an object, pass any transient\nparams in the second argument:\n\n```typescript\nconst user = factories.user.build({}, { transient: { registered: true } });\n```\n\nTransient params are passed in to your factory and can then be used\nhowever you like:\n\n```typescript\ntype User = {\n  name: string;\n  posts: Post[];\n  memberId: string | null;\n  permissions: { canPost: boolean };\n};\n\ntype UserTransientParams = {\n  registered: boolean;\n  numPosts: number;\n};\n\nconst userFactory = Factory.define\u003cUser, UserTransientParams\u003e(\n  ({ transientParams, sequence }) =\u003e {\n    const { registered, numPosts = 1 } = transientParams;\n\n    const user = {\n      name: 'Susan Velasquez',\n      posts: postFactory.buildList(numPosts),\n      memberId: registered ? `member-${sequence}` : null,\n      permissions: {\n        canPost: registered,\n      },\n    };\n\n    return user;\n  },\n);\n```\n\nIn the example above, we also created a type called `UserTransientParams` and\npassed it as the second generic type to `define`. This gives you type\nchecking of transient params, both in the factory and when calling `build`.\n\nWhen constructing objects, any regular params you pass to `build` take\nprecedence over the transient params:\n\n```typescript\nconst user = userFactory.build(\n  { memberId: '1' },\n  { transient: { registered: true } },\n);\n\nuser.memberId; // '1'\nuser.permissions.canPost; // true\n```\n\nPassing transient params to `build` can be a bit verbose. It is often a good\nidea to consider creating a [reusable builder method](#adding-reusable-builders-traits-to-factories) instead of or in\naddition to your transient params to make building objects simpler.\n\n### After-build hook\n\nYou can instruct factories to execute some code after an object is built.\nThis can be useful if a reference to the object is needed, like when setting\nup relationships:\n\n```typescript\nconst userFactory = Factory.define\u003cUser\u003e(({ sequence, afterBuild }) =\u003e {\n  afterBuild(user =\u003e {\n    const post = factories.post.build({}, { associations: { author: user } });\n    user.posts.push(post);\n  });\n\n  return {\n    id: sequence,\n    name: 'Bob',\n    posts: [],\n  };\n});\n```\n\n### After-create hook\n\nSimilar to `onCreate`, `afterCreate`s can also be defined. These are executed after the `onCreate`, and multiple can be defined for a given factory.\n\n```typescript\nconst userFactory = Factory.define\u003cUser, {}, SavedUser\u003e(\n  ({ sequence, onCreate, afterCreate }) =\u003e {\n    onCreate(user =\u003e apiService.create(user));\n    afterCreate(savedUser =\u003e doMoreStuff(savedUser));\n\n    return {\n      id: sequence,\n      name: 'Bob',\n      posts: [],\n    };\n  },\n);\n\n// can define additional afterCreates\nconst savedUser = userFactory\n  .afterCreate(async savedUser =\u003e savedUser)\n  .create();\n```\n\n### Extending factories\n\nFactories can be extended using the extension methods: `params`, `transient`,\n`associations`, `afterBuild`, `afterCreate` and `onCreate`. These set default\nattributes that get passed to the factory on `build`. They return a new factory\nand do not modify the factory they are called on :\n\n```typescript\nconst userFactory = Factory.define\u003cUser\u003e(() =\u003e ({\n  admin: false,\n}));\n\nconst adminFactory = userFactory.params({ admin: true });\nadminFactory.build().admin; // true\nuserFactory.build().admin; // false\n```\n\n`params`, `associations`, and `transient` behave in the same way as the arguments to `build`. The following are equivalent:\n\n```typescript\nconst user = userFactory\n  .params({ admin: true })\n  .associations({ post: postFactory.build() })\n  .transient({ name: 'Jared' })\n  .build();\n\nconst user2 = userFactory.build(\n  { admin: true },\n  {\n    associations: { post: postFactory.build() },\n    transient: { name: 'Jared' },\n  },\n);\n```\n\nAdditionally, the following extension methods are available:\n\n- `afterBuild` - executed after an object is built. Multiple can be defined\n- `onCreate` - defines or replaces the behavior of `create()`. Must be defined prior to calling `create()`. Only one can be defined.\n- `afterCreate` - called after `onCreate()` before the object is returned from `create()`. Multiple can be defined\n\nThese extension methods can be called multiple times to continue extending\nfactories:\n\n```typescript\nconst sallyFactory = userFactory\n  .params({ admin: true })\n  .params({ name: 'Sally' })\n  .afterBuild(user =\u003e console.log('hello'))\n  .afterBuild(user =\u003e console.log('there'));\n\nconst user = sallyFactory.build();\n// log: hello\n// log: there\nuser.name; // Sally\nuser.admin; // true\n\nconst user2 = sallyFactory.build({ admin: false });\n// log: hello\n// log: there\nuser.name; // Sally\nuser2.admin; // false\n```\n\n### Adding reusable builders (traits) to factories\n\nIf you find yourself frequently building objects with a certain set of\nproperties, it might be time to either extend the factory or create a\nreusable builder method.\n\nFactories are just classes, so adding reusable builder methods can be achieved by subclassing `Factory` and defining any desired methods:\n\n```typescript\nclass UserFactory extends Factory\u003cUser, UserTransientParams\u003e {\n  admin(adminId?: string) {\n    return this.params({\n      admin: true,\n      adminId: adminId || `admin-${this.sequence()}`,\n    });\n  }\n\n  registered() {\n    return this\n      .params({ memberId: this.sequence() })\n      .transient({ registered: true })\n      .associations({ profile: profileFactory.build() })\n      .afterBuild(user =\u003e console.log(user))\n  }\n}\n\n// instead of Factory.define\u003cUser\u003e\nconst userFactory = UserFactory.define(() =\u003e ({ ... }))\n\nconst user = userFactory.admin().registered().build()\n```\n\nTo learn more about the factory builder methods `params`, `transient`,\n`associations`, `afterBuild`, `onCreate`, and `afterCreate`, see [Extending factories](#extending-factories), above.\n\n## Advanced\n\n### Associations\n\nFactories can import and reference other factories for associations:\n\n```typescript\nimport userFactory from './user';\n\nconst postFactory = Factory.define\u003cPost\u003e(() =\u003e ({\n  title: 'My Blog Post',\n  author: userFactory.build(),\n}));\n```\n\nIf you'd like to be able to pass in an association when building your object and\nshort-circuit the call to `yourFactory.build()`, use the `associations`\nvariable provided to your factory:\n\n```typescript\nconst postFactory = Factory.define\u003cPost\u003e(({ associations }) =\u003e ({\n  title: 'My Blog Post',\n  author: associations.author || userFactory.build(),\n}));\n```\n\nThen build your object like this:\n\n```typescript\nconst jordan = userFactory.build({ name: 'Jordan' });\nfactories.post.build({}, { associations: { author: jordan } });\n```\n\nIf two factories reference each other, they can usually import each other\nwithout issues, but TypeScript might require you to explicitly type your\nfactory before exporting so it can determine the type before the circular\nreferences resolve:\n\n```typescript\n// the extra Factory\u003cPost\u003e typing can be necessary with circular imports\nconst postFactory: Factory\u003cPost\u003e = Factory.define\u003cPost\u003e(() =\u003e ({ ...}));\nexport default postFactory;\n```\n\n### Rewind Sequence\n\nA factory's sequence can be rewound with `rewindSequence()`.\nThis sets the sequence back to its original starting value.\n\n## Contributing\n\nSee the [CONTRIBUTING] document.\nThank you, [contributors]!\n\n[contributing]: CONTRIBUTING.md\n[contributors]: https://github.com/thoughtbot/fishery/graphs/contributors\n\n## Credits\n\nThis project name was inspired by Patrick Rothfuss' _Kingkiller Chronicles_\nbooks. In the books, the artificery, or workshop, is called the Fishery for\nshort. The Fishery is where things are built.\n\n## License\n\nFishery is Copyright © 2021 Stephen Hanson and thoughtbot. It is free\nsoftware, and may be redistributed under the terms specified in the\n[LICENSE](/LICENSE) file.\n\n\u003c!-- START /templates/footer.md --\u003e\n\n## About thoughtbot\n\n![thoughtbot](https://thoughtbot.com/thoughtbot-logo-for-readmes.svg)\n\nThis repo is maintained and funded by thoughtbot, inc.\nThe names and logos for thoughtbot are trademarks of thoughtbot, inc.\n\nWe love open source software!\nSee [our other projects][community].\nWe are [available for hire][hire].\n\n[community]: https://thoughtbot.com/community?utm_source=github\n[hire]: https://thoughtbot.com/hire-us?utm_source=github\n\n\u003c!-- END /templates/footer.md --\u003e\n\n[factory_bot]: https://github.com/thoughtbot/factory_bot\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoughtbot%2Ffishery","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthoughtbot%2Ffishery","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthoughtbot%2Ffishery/lists"}