factory-bot-ts

A simple library for setting up TypeScript objects as test data - heavily inspired by the awesome Ruby's factory_bot

Usage no npm install needed!

<script type="module">
  import factoryBotTs from 'https://cdn.skypack.dev/factory-bot-ts';
</script>

README

factory-bot-ts

A simple library for setting up TypeScript objects as test data - heavily inspired by the awesome Ruby's factory_bot.

  • Fully written in TypeScript
  • With (optional) type checking
  • With no persistence layer
  • And no promises. ☺️

Installation

1. Add to your project

npm i -D factory-bot-ts
yarn add factory-bot-ts --dev

Basics

1. Ok, suppose we've got a Ninja model..

  // ../src/models/ninja.model.ts

  export enum NinjaRank {
    GENIN = 'Genin',
    CHUUNIN = 'Chuunin',
    JONIN = 'Jonin'
  }

  export class Ninja {
    id?: number
    name?: string
    username?: string
    level?: NinjaRank
    sensor?: boolean

    constructor(attrs?: Partial<Ninja>) {
      Object.assign(this, attrs)
    }
  }

2. We would define our factories..

  // ../src/tests/index.ts

  import * as Faker from 'faker'
  import { sample } from 'lodash'

  import { FactoryBot } from 'factory-bot-ts'

  import { Ninja, NinjaRank, Village } from '../src/models'

  FactoryBot.define('ninja', {
    id: () => Faker.random.number(),
    name: () => Faker.name.findName(),
    username: () => FactoryBot.seq(seq => `${Faker.internet.userName()}_${seq}`),
    level: () => FactoryBot.rand(NinjaRank),
    sensor: () => sample([true, false])
  }, Ninja)

  FactoryBot.define('village', {
    id: () => Faker.random.uuid(),
    name: () => Faker.name.findName(),
    members: () => FactoryBot.buildList<Ninja>('ninja', 2)
  }, Village)

  export {
    FactoryBot
  }

3. And then we'd just use them on our tests!

  // ../src/models/ninja.model.spec.ts

  import { FactoryBot } from '../src/tests'

  import { Ninja, NinjaRank, Village } from '.'

  export class NinjaSpec {
    let instance: Ninja

    before(() => {
      instance = FactoryBot.build('ninja', { name: 'Kakashi Hatake' })
    })

    it('builds a Ninja! 👹', () => {
      expect(instance).to.be.an
        .instanceof(Ninja)
    })
  }

More

Factory-bot-ts also allow us to..

1. Define untyped factories with static data

  FactoryBot.define('ninja', {
    id: 1,
    name: 'Sasuke Uchiha',
    username: 'sasuke',
    level: NinjaRank.GENIN,
    sensor: false,
    sharingan: true
  })

  FactoryBot.build('ninja') /* => {
    id: 1,
    name: 'Sasuke Uchiha',
    username: 'sasuke',
    level: 'Genin',
    sensor: false,
    sharingan: true
  } */

2. Define factories with type checking

  FactoryBot.define<Ninja>('ninja', {
    id: 1,
    name: 'Sasuke Uchiha',
    username: 'sasuke',
    level: NinjaRank.GENIN,
    sensor: false
  }, Ninja)

  FactoryBot.build('ninja') /* => Ninja {
    id: 1,
    name: 'Sasuke Uchiha',
    username: 'sasuke',
    level: 'Genin',
    sensor: false
  } */

3. Define factories with Dynamic data

  FactoryBot.define('ninja', {
    id: () => Faker.random.number(),
    name: () => Faker.name.findName(),
    username: () => Faker.internet.userName(),
    level: () => FactoryBot.rand(NinjaRank),
    sensor: () => sample([true, false])
  }, Ninja)

  FactoryBot.build('ninja') /* => Ninja {
    id: 43748,
    name: 'Martine Romaguera MD',
    username: 'Kaleb_Homenick',
    level: 'Jonin',
    sensor: true
  } */

4. Define factories with custom, random and sequenced data

  FactoryBot.define('ninja', {
    id: () => Faker.random.number(),
    name: () => Faker.name.findName(),
    username: () => FactoryBot.seq(seq => `${Faker.internet.userName()}_${seq}`),
    level: () => FactoryBot.rand(NinjaRank),
    sensor: () => sample([true, false])
  }, Ninja)

  FactoryBot.build<Ninja>('ninja', { name: 'Uzimaki Naruto' }) /* => Ninja {
    id: 11941,
    name: 'Uzimaki Naruto',
    username: 'Art_Crist36_1',
    level: 'Genin',
    sensor: true
  } */

  FactoryBot.build<Ninja>('ninja', { name: 'Sasuke Uchiha', username: 'sasuke-kun' }) /* => Ninja {
    id: 52565,
    name: 'Sasuke Uchiha',
    username: 'sasuke-kun',
    level: 'Chuunin',
    sensor: false
  } */

5. Define chained factories

  FactoryBot.define('ninja', {
    id: () => Faker.random.number(),
    name: () => Faker.name.findName(),
    username: () => FactoryBot.seq(seq => `${Faker.internet.userName()}_${seq}`),
    level: () => FactoryBot.rand(NinjaRank),
    sensor: () => sample([true, false])
  }, Ninja)

  FactoryBot.define('village', {
    id: () => Faker.random.uuid(),
    name: 'Leaf',
    members: () => FactoryBot.buildList<Ninja>('ninja', 2)
  }, Village)

  FactoryBot.build('village') /* => Village {
    id: '7ec17407-cfdb-4a3f-b434-de788eb41591',
    name: 'Leaf',
    members: [ Ninja {
      id: 11941,
      name: 'Herta Hane',
      username: 'Kaleb_Homenick_5',
      level: 'Genin',
      sensor: true
    }, Ninja {
      id: 52565,
      name: 'Corbin Koss',
      username: 'Art_Crist36_6',
      level: 'Genin',
      sensor: false
    }]
  } */

6. Extend existing factories in order to generate specialized data

  FactoryBot.define<Ninja>('ninja', {
    id: 1,
    name: 'Kakashi Hatake',
    username: 'kakashi',
    level: NinjaRank.GENIN,
    sensor: false
  }, Ninja)

  FactoryBot.extend<Ninja>('ninja', 'jōnin', {
    level: NinjaRank.JONIN
  })

  FactoryBot.build<Ninja>('ninja') /* => Ninja {
    id: 1,
    name: 'Kakashi Hatake',
    username: 'kakashi',
    level: Genin,
    sensor: false
  } */

  FactoryBot.build<Ninja>('jōnin') /* => Ninja {
    id: 1,
    name: 'Kakashi Hatake',
    username: 'kakashi',
    level: Jōnin,
    sensor: false
  } */

For more examples, please, check out the project's specs.

Dependencies

To run this project we need to have:

Development

  1. Install the dependencies above
  2. $ git clone https://github.com/roalcantara/factory-bot-ts.git - Clone the project
  3. $ cd factory-bot-ts - Go into the project folder
  4. $ yarn - Run the setup script

Running specs

$ yarn test to run the specs

How to contribute

Code of Conduct

Everyone interacting in the factory-bot-ts project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

License

The package is available as open source under the terms of the CC BY-SA 4.0.