factory-bot-ts
Version:
A simple library for setting up TypeScript objects as test data - heavily inspired by the awesome Ruby's factory_bot
407 lines (345 loc) • 10.5 kB
text/typescript
// tslint:disable:max-classes-per-file
import { sample } from 'lodash'
import { FactoryBot } from '../src'
import { expect, Faker } from '.'
export enum NinjaRank {
GENIN = 'Genin',
CHUUNIN = 'Chuunin',
JONIN = 'Jōnin'
}
export class Ninja {
id?: number
name?: string
username?: string
level?: NinjaRank
sensor?: boolean
constructor(attrs?: Partial<Ninja>) {
Object.assign(this, attrs)
}
}
export class Village {
id?: string
name?: string
members?: Array<Ninja>
constructor(attrs?: Partial<Village>) {
Object.assign(this, attrs)
}
}
describe('FactoryBot', () => {
describe('#seq', () => {
let first
before(() => {
first = FactoryBot.seq(seq => `ninja${seq}@leaf.jp`)
})
it('generates a sequenced value for each invocation', () => {
expect(first).to
.eq('ninja1@leaf.jp')
})
context('when seq is called again', () => {
let second
before(() => {
second = FactoryBot.seq(seq => `ninja${seq}@leaf.jp`)
})
it('does not duplicate values', () => {
expect(first).to.not
.eq(second)
})
})
})
describe('#rand', () => {
let value
context('when a Enum is given', () => {
before(() => {
value = FactoryBot.rand(NinjaRank)
})
it('returns a random enum value', () => {
expect([NinjaRank.CHUUNIN, NinjaRank.GENIN, NinjaRank.JONIN]).to
.include(value)
})
})
})
describe('#define', () => {
context('when static values are given', () => {
before(() => {
FactoryBot.define<Ninja>('ninja', {
id: 1,
name: 'Uzimaki Naruto',
username: 'naruto',
level: NinjaRank.GENIN,
sensor: false
}, Ninja)
})
it('defines a factory with same values for each build', () => {
expect(FactoryBot.build<Ninja>('ninja')).to.deep
.eq(new Ninja({
id: 1,
name: 'Uzimaki Naruto',
username: 'naruto',
level: NinjaRank.GENIN,
sensor: false
}))
})
})
context('when no model class is given', () => {
before(() => {
FactoryBot.define('ninja', {
id: '1234',
name: 'Uzimaki Naruto',
username: 'naruto',
level: NinjaRank.GENIN,
sensor: false
})
})
it('defines a simple factory', () => {
expect(FactoryBot.build('ninja')).to.deep
.eq({
id: '1234',
name: 'Uzimaki Naruto',
username: 'naruto',
level: NinjaRank.GENIN,
sensor: false
})
})
it('with no model class', () => {
expect(FactoryBot.build('ninja')).to.not.be.an
.instanceof(Ninja)
})
})
context('when no generic type is given', () => {
before(() => {
FactoryBot.define('ninja', {
id: '1234',
name: 'Uzimaki Naruto',
username: 'naruto',
level: NinjaRank.GENIN,
email: 'non-existent@property.jp'
})
})
it('defines a factory with no type checking', () => {
expect(FactoryBot.build('ninja')).to.deep
.eq({
id: '1234',
name: 'Uzimaki Naruto',
username: 'naruto',
level: NinjaRank.GENIN,
email: 'non-existent@property.jp'
})
})
context('with function values', () => {
before(() => {
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)
})
it('defines a factory with values that are evaluated for each build', () => {
const first = FactoryBot.build('ninja', { name: 'Uzimaki Naruto', username: 'naruto-kun' })
const second = FactoryBot.build('ninja', { name: 'Sasuke Uchiha', username: 'sasuke-kun' })
expect(first).to.not.be.deep
.eq(second)
})
})
})
context('when a chained factory is defined', () => {
let village: Village
before(() => {
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', 2)
}, Village)
village = FactoryBot.build<Village>('village')
})
it('defines a factory with nested values', () => {
expect(village.members.length).to
.eq(2)
})
})
})
describe('#count', () => {
context('when there are no factories defined', () => {
before(() => {
FactoryBot.clear()
})
it('returns zero', () => {
expect(FactoryBot.count()).to
.eq(0)
})
})
context('when there are factories are defined', () => {
before(() => {
FactoryBot.define<Ninja>('ninja', {
id: 1,
name: 'Kakashi Hatake',
username: 'kakashi',
level: NinjaRank.GENIN,
sensor: false
}, Ninja)
FactoryBot.define('village', {
id: () => Faker.random.uuid(),
name: 'Leaf',
members: () => FactoryBot.buildList('ninja', 2)
}, Village)
})
it('returns the quantity of defined factories', () => {
expect(FactoryBot.count()).to
.eq(2)
})
})
})
describe('#clear', () => {
context('when there are factories defined', () => {
before(() => {
FactoryBot.define<Ninja>('ninja', {
id: 1,
name: 'Kakashi Hatake',
username: 'kakashi',
level: NinjaRank.GENIN,
sensor: false
}, Ninja)
})
it('cleans up all factories', () => {
FactoryBot.clear()
expect(FactoryBot.count()).to
.eq(0)
})
})
})
describe('#extend', () => {
context('when a factory already exist', () => {
before(() => {
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.extend<Ninja>('ninja', 'chuunin:sensor', {
level: NinjaRank.CHUUNIN,
sensor: true
})
})
it('extends existing factories in order to generate specialized data', () => {
expect(FactoryBot.build<Ninja>('jōnin')).to.deep
.eq(new Ninja({
id: 1,
name: 'Kakashi Hatake',
username: 'kakashi',
level: NinjaRank.JONIN,
sensor: false
}))
expect(FactoryBot.build<Ninja>('chuunin:sensor')).to.deep
.eq(new Ninja({
id: 1,
name: 'Kakashi Hatake',
username: 'kakashi',
level: NinjaRank.CHUUNIN,
sensor: true
}))
})
context('when the trait already exists', () => {
it('throws an error', () => {
expect(() => {
FactoryBot.extend<Ninja>('ninja', 'jōnin', {
level: NinjaRank.JONIN
})
}).to
.throw(Error, "Factory 'ninja'`s trait 'jōnin' has already been defined!")
})
})
})
context('when the factory does not exist', () => {
beforeEach(() => {
FactoryBot.clear()
})
it('throws an error', () => {
expect(() => {
FactoryBot.extend<Ninja>('ninja', 'jōnin', {
level: NinjaRank.JONIN
})
}).to
.throw(Error, "Factory 'ninja' has not been defined!")
})
})
})
describe('#build', () => {
before(() => {
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)
})
it('builds and returns a object`s instance for the given factory', () => {
expect(FactoryBot.build<Ninja>('ninja')).to.be.an
.instanceof(Ninja)
})
context('when custom parameters are given', () => {
let ninja: Ninja
before(() => {
ninja = FactoryBot.build<Ninja>('ninja', {
name: 'Uzimaki Naruto',
username: 'naruto-kun'
})
})
it('builds and returns a object`s intance with the given custom parameters', () => {
expect(ninja).to.be.deep
.eq(new Ninja({
id: ninja.id,
name: 'Uzimaki Naruto',
username: 'naruto-kun',
level: ninja.level,
sensor: ninja.sensor
}))
})
})
})
describe('#buildList', () => {
let ninjas: Array<Ninja>
before(() => {
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)
ninjas = FactoryBot.buildList<Ninja>('ninja')
})
it('builds a list with at least one object`s instance', () => {
expect(ninjas.length).to.be
.eq(1)
})
context('when a length is given', () => {
let length: number
before(() => {
length = 3
ninjas = FactoryBot.buildList<Ninja>('ninja', length)
})
it('builds a list with the given length of object`s instances', () => {
expect(ninjas.length).to.be
.eq(length)
})
it('builds a list with different objetcs', () => {
expect(ninjas[0].id).to.not.be
.eq(ninjas[1].id)
})
})
})
})