@jackfranklin/test-data-bot
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -13,8 +13,8 @@ export interface SequenceGenerator<T> { | ||
} | ||
export declare type FieldGenerator<T> = SequenceGenerator<T> | OneOfGenerator<T> | PerBuildGenerator<T>; | ||
export declare type Field<T = any> = T | FieldGenerator<T> | FieldsConfiguration<T>; | ||
export declare type FieldsConfiguration<FactoryResultType> = { | ||
export type FieldGenerator<T> = SequenceGenerator<T> | OneOfGenerator<T> | PerBuildGenerator<T>; | ||
export type Field<T = any> = T | FieldGenerator<T> | FieldsConfiguration<T>; | ||
export type FieldsConfiguration<FactoryResultType> = { | ||
readonly [Key in keyof FactoryResultType]: Field<FactoryResultType[Key]>; | ||
}; | ||
export declare type Overrides<FactoryResultType = any> = { | ||
export type Overrides<FactoryResultType = any> = { | ||
[Key in keyof FactoryResultType]?: Field<FactoryResultType[Key]>; | ||
@@ -38,7 +38,13 @@ }; | ||
} | ||
export declare type ValueOf<T> = T[keyof T]; | ||
export declare const build: <FactoryResultType>(factoryNameOrConfig: string | BuildConfiguration<FactoryResultType>, configObject?: BuildConfiguration<FactoryResultType> | undefined) => (buildTimeConfig?: BuildTimeConfig<FactoryResultType> | undefined) => FactoryResultType; | ||
export type ValueOf<T> = T[keyof T]; | ||
export interface Builder<FactoryResultType> { | ||
(buildTimeConfig?: BuildTimeConfig<FactoryResultType>): FactoryResultType; | ||
reset(): void; | ||
one(buildTimeConfig?: BuildTimeConfig<FactoryResultType>): FactoryResultType; | ||
many(count: number, buildTimeConfig?: BuildTimeConfig<FactoryResultType>): FactoryResultType[]; | ||
} | ||
export declare const build: <FactoryResultType>(factoryNameOrConfig: string | BuildConfiguration<FactoryResultType>, configObject?: BuildConfiguration<FactoryResultType> | undefined) => Builder<FactoryResultType>; | ||
export declare const oneOf: <T>(...options: T[]) => OneOfGenerator<T>; | ||
export declare const bool: () => OneOfGenerator<boolean>; | ||
declare type Sequence = { | ||
type Sequence = { | ||
(): SequenceGenerator<number>; | ||
@@ -45,0 +51,0 @@ <T>(userProvidedFunction: (count: number) => T): SequenceGenerator<T>; |
@@ -72,2 +72,5 @@ "use strict"; | ||
} | ||
if (fieldValue instanceof Date) { | ||
return fieldValue; | ||
} | ||
if (typeof fieldValue === 'object') { | ||
@@ -78,5 +81,22 @@ return expandConfigFields(fieldValue); | ||
}; | ||
return (buildTimeConfig = {}) => { | ||
const builder = (buildTimeConfig = {}) => { | ||
const fieldsToReturn = expandConfigFields(config.fields, buildTimeConfig); | ||
const traitsArray = buildTimeTraitsArray(buildTimeConfig); | ||
// A user might define a value in a trait that doesn't exist in the base | ||
// set of fields. So we need to check now if the traits set any values that | ||
// aren't in the base, and set them too. | ||
traitsArray.forEach((traitName) => { | ||
const traitConfig = (config.traits && config.traits[traitName]) || {}; | ||
if (!traitConfig.overrides) { | ||
return; | ||
} | ||
for (const stringKey of Object.keys(traitConfig.overrides)) { | ||
const key = stringKey; | ||
// If the key already exists in the base fields, we'll have defined it, | ||
// so we don't need to worry about it. | ||
if (key in config.fields === false) { | ||
fieldsToReturn[key] = expandConfigField(traitConfig.overrides[key]); | ||
} | ||
} | ||
}); | ||
const traitPostBuilds = traitsArray.map((traitName) => { | ||
@@ -94,2 +114,12 @@ const traitConfig = (config.traits && config.traits[traitName]) || {}; | ||
}; | ||
builder.reset = () => { | ||
sequenceCounter = 0; | ||
}; | ||
builder.one = (buildTimeConfig) => { | ||
return builder(buildTimeConfig); | ||
}; | ||
builder.many = (times, buildTimeConfig) => { | ||
return new Array(times).fill(0).map(() => builder(buildTimeConfig)); | ||
}; | ||
return builder; | ||
}; | ||
@@ -96,0 +126,0 @@ exports.build = build; |
{ | ||
"name": "@jackfranklin/test-data-bot", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"license": "MIT", | ||
@@ -11,5 +11,6 @@ "description": "Generate test data for your tests easily.", | ||
"scripts": { | ||
"test": "jest src", | ||
"lint": "eslint 'src/*.ts' && prettier --list-different 'src/*.ts'", | ||
"lint-fix": "eslint --fix 'src/*.ts' && prettier --write 'src/*.ts'", | ||
"test": "tap --no-coverage -R=dot", | ||
"test-with-coverage": "tap -R=dot", | ||
"lint": "eslint '{src,test}/*.ts' && prettier --list-different '{src,test}/*.ts'", | ||
"lint-fix": "eslint --fix '{src,test}/*.ts' && prettier --write '{src,test}/*.ts'", | ||
"build": "tsc --build tsconfig.json", | ||
@@ -19,2 +20,5 @@ "prepare": "npm run build", | ||
}, | ||
"tap": { | ||
"ts": true | ||
}, | ||
"keywords": [ | ||
@@ -37,4 +41,6 @@ "testing", | ||
"devDependencies": { | ||
"@types/jest": "^27.0.3", | ||
"@types/node": "^17.0.9", | ||
"@sinonjs/fake-timers": "^10.0.0", | ||
"@types/node": "^18.0.0", | ||
"@types/sinon": "^10.0.13", | ||
"@types/tap": "^15.0.7", | ||
"@typescript-eslint/eslint-plugin": "^5.6.0", | ||
@@ -45,9 +51,10 @@ "@typescript-eslint/parser": "^5.6.0", | ||
"eslint-config-unobtrusive": "^1.2.5", | ||
"eslint-plugin-jest": "^25.3.0", | ||
"eslint-plugin-prettier": "^4.0.0", | ||
"jest": "^27.4.3", | ||
"eslint-plugin-tap": "^1.2.1", | ||
"prettier": "^2.5.1", | ||
"ts-jest": "^27.1.1", | ||
"typescript": "^4.6.2" | ||
"sinon": "^15.0.1", | ||
"tap": "^16.3.0", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.8.4" | ||
} | ||
} |
304
README.md
# @jackfranklin/test-data-bot | ||
[![CircleCI](https://circleci.com/gh/jackfranklin/test-data-bot.svg?style=svg)](https://circleci.com/gh/jackfranklin/test-data-bot) | ||
[![npm version](https://badge.fury.io/js/%40jackfranklin%2Ftest-data-bot.svg)](https://badge.fury.io/js/%40jackfranklin%2Ftest-data-bot) | ||
@@ -35,3 +33,3 @@ | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -42,3 +40,3 @@ name: 'jack', | ||
const user = userBuilder(); | ||
const user = userBuilder.one(); | ||
console.log(user); | ||
@@ -50,16 +48,10 @@ // => { name: 'jack'} | ||
**Note**: if you're using *Version 1.2 or higher* you can leave the name of the factory and pass in only the configuration object: | ||
Once you've created a builder, you can call the `one` method it returns to generate an instance of that object - in this case, a `user`. | ||
```js | ||
const userBuilder = build({ | ||
fields: { | ||
name: 'jack', | ||
}, | ||
}); | ||
``` | ||
const user = userBuilder.one(); | ||
``` | ||
Feel free to use the name property if you like, but it's not used for anything in test-data-bot. It will probably get removed in a future major version. | ||
> You can also call the builder directly to get a single instance - `userBuilder()`. v2.1 of test-data-bot shipped with the `one()` method and that is now the recommended way of constructing these objects. | ||
Once you've created a builder, you can call it to generate an instance of that object - in this case, a `user`. | ||
It would be boring though if each user had the same `name` - so test-data-bot lets you generate data via some API methods: | ||
@@ -74,3 +66,3 @@ | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -81,4 +73,4 @@ id: sequence(), | ||
const userOne = userBuilder(); | ||
const userTwo = userBuilder(); | ||
const userOne = userBuilder.one(); | ||
const userTwo = userBuilder.one(); | ||
@@ -94,3 +86,3 @@ // userOne.id === 1 | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -101,4 +93,4 @@ email: sequence(x => `jack${x}@gmail.com`), | ||
const userOne = userBuilder(); | ||
const userTwo = userBuilder(); | ||
const userOne = userBuilder.one(); | ||
const userTwo = userBuilder.one(); | ||
@@ -109,4 +101,27 @@ // userOne.email === jack1@gmail.com | ||
### Randomly picking between an option | ||
You can use the `reset` method to reset the counter used internally when generating a sequence: | ||
```js | ||
const { build, sequence } = require('@jackfranklin/test-data-bot'); | ||
const userBuilder = build({ | ||
fields: { | ||
id: sequence(), | ||
}, | ||
}); | ||
const userOne = userBuilder.one(); | ||
const userTwo = userBuilder.one(); | ||
userBuilder.reset(); | ||
const userThree = userBuilder.one(); | ||
const userFour = userBuilder.one(); | ||
// userOne.id === 1 | ||
// userTwo.id === 2 | ||
// userThree.id === 1 <- the sequence has been reset here | ||
// userFour.id === 2 | ||
``` | ||
### Randomly picking between an option with `oneOf` | ||
If you want an object to have a random value, picked from a list you control, you can use `oneOf`: | ||
@@ -117,3 +132,3 @@ | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -132,3 +147,3 @@ name: oneOf('alice', 'bob', 'charlie'), | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -147,3 +162,3 @@ isAdmin: bool(), | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -159,4 +174,4 @@ name: 'jack', | ||
```js | ||
const userOne = userBuilder(); | ||
const userTwo = userBuilder(); | ||
const userOne = userBuilder.one(); | ||
const userTwo = userBuilder.one(); | ||
@@ -171,3 +186,3 @@ userOne.details === userTwo.details; // true | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -181,4 +196,4 @@ name: 'jack', | ||
const userOne = userBuilder(); | ||
const userTwo = userBuilder(); | ||
const userOne = userBuilder.one(); | ||
const userTwo = userBuilder.one(); | ||
@@ -194,3 +209,3 @@ userOne.details === userTwo.details; // false | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -202,23 +217,2 @@ name: perBuild(() => myFakeLibrary.randomName()), | ||
### Mapping over all the created objects with `postBuild` | ||
If you need to transform an object in a way that test-data-bot doesn't support out the box, you can pass a `postBuild` function when creating a builder. This builder will run everytime you create an object from it. | ||
```js | ||
const { build, fake } = require('@jackfranklin/test-data-bot'); | ||
const userBuilder = build('User', { | ||
fields: { | ||
name: fake(f => f.name.findName()), | ||
}, | ||
postBuild: user => { | ||
user.name = user.name.toUpperCase(); | ||
return user; | ||
}, | ||
}); | ||
const user = userBuilder(); | ||
// user.name will be uppercase | ||
``` | ||
## Overrides per-build | ||
@@ -231,3 +225,3 @@ | ||
const userBuilder = build('User', { | ||
const userBuilder = build({ | ||
fields: { | ||
@@ -239,3 +233,3 @@ id: sequence(), | ||
const user = userBuilder({ | ||
const user = userBuilder.one({ | ||
overrides: { | ||
@@ -251,6 +245,6 @@ id: 1, | ||
If you need to edit the object directly, you can pass in a `map` function when you call the builder: | ||
If you need to edit the object directly, you can pass in a `map` function when you call the builder. This will be called after test-data-bot has generated the fake object, and lets you directly change its properties. | ||
```js | ||
const { build, fake, sequence } = require('@jackfranklin/test-data-bot'); | ||
const { build, sequence } = require('@jackfranklin/test-data-bot'); | ||
@@ -260,7 +254,7 @@ const userBuilder = build('User', { | ||
id: sequence(), | ||
name: fake(f => f.name.findName()), | ||
name: 'jack', | ||
}, | ||
}); | ||
const user = userBuilder({ | ||
const user = userBuilder.one({ | ||
map: user => { | ||
@@ -275,2 +269,54 @@ user.name = user.name.toUpperCase(); | ||
### Creating multiple instances | ||
If you want to create multiple instances of a builder at once, you can use the `many` method on the builder: | ||
```js | ||
const userBuilder = build({ | ||
fields: { | ||
name: 'jack', | ||
}, | ||
}); | ||
const users = userBuilder.many(20); // Creates an array of 20 users. | ||
``` | ||
If you want to pass in any build time configuration, you can pass in a second argument which takes the exact same configuration as calling `userBuilder()` directly: | ||
```js | ||
const userBuilder = build({ | ||
fields: { | ||
name: 'jack', | ||
}, | ||
}); | ||
const users = userBuilder.many(20, { | ||
overrides: { | ||
name: 'bob' | ||
} | ||
}); // Creates an array of 20 users, each called "bob"! | ||
``` | ||
### Mapping over all the created objects with `postBuild` | ||
If you need to transform an object in a way that test-data-bot doesn't support out the box, you can pass a `postBuild` function when creating a builder. This builder will run every time you create an object from it. | ||
```js | ||
const { build, fake } = require('@jackfranklin/test-data-bot'); | ||
const userBuilder = build({ | ||
fields: { | ||
name: fake(f => f.name.findName()), | ||
}, | ||
postBuild: user => { | ||
user.name = user.name.toUpperCase(); | ||
return user; | ||
}, | ||
}); | ||
const user = userBuilder.one(); | ||
// user.name will be uppercase | ||
``` | ||
## Traits (*new in v1.3*) | ||
@@ -289,7 +335,7 @@ | ||
name: 'jack', | ||
admin: perBuild(() => false), | ||
admin: false, | ||
}, | ||
traits: { | ||
admin: { | ||
overrides: { admin: perBuild(() => true) }, | ||
overrides: { admin: true }, | ||
}, | ||
@@ -303,3 +349,3 @@ }, | ||
```js | ||
const adminUser = userBuilder({ overrides: { admin: perBuild(() => true) } }); | ||
const adminUser = userBuilder.one({ overrides: { admin: true } }); | ||
``` | ||
@@ -313,7 +359,7 @@ | ||
name: 'jack', | ||
admin: perBuild(() => false), | ||
admin: false, | ||
}, | ||
traits: { | ||
admin: { | ||
overrides: { admin: perBuild(() => true) }, | ||
overrides: { admin: true }, | ||
}, | ||
@@ -327,3 +373,3 @@ }, | ||
```js | ||
const admin = userBuilder({ traits: 'admin' }); | ||
const admin = userBuilder.one({ traits: 'admin' }); | ||
``` | ||
@@ -335,3 +381,3 @@ | ||
// any properties defined in other-trait will override any that admin sets | ||
const admin = userBuilder({ traits: ['admin', 'other-trait'] }); | ||
const admin = userBuilder.one({ traits: ['admin', 'other-trait'] }); | ||
``` | ||
@@ -354,7 +400,7 @@ | ||
id: sequence(), | ||
name: fake(f => f.name.findName()), | ||
name: perBuild(() => yourCustomFakerLibary().name) | ||
}, | ||
}); | ||
const users = userBuilder(); | ||
const users = userBuilder.one(); | ||
``` | ||
@@ -364,124 +410,16 @@ | ||
_I'm still quite new to TypeScript so please let me know if you don't get the errors/type hints that you expect!_ | ||
## What happened to Faker / the `fake` generator? | ||
# Migrating from `test-data-bot@0.8.0` to `@jackfranklin/test-data-bot@1.0.0` | ||
Prior to v2.0.0 of this library, we shipped built-in support for using Faker.js to generate data. It was removed because it was a big dependency to ship to all users, even those who don't use faker. If you want to use it you can, in combination with the `perBuild` builder: | ||
Firstly: there is no need to migrate immediately if the old version is doing the job for you - but it won't be improved or have bug fixes so it's recommended to migrate slowly to 1.0.0. | ||
You can also run both versions at the same time - they won't conflict. Just make sure you rename the API methods or import everything: | ||
```js | ||
const legacyTestDataBot = require('test-data-bot') | ||
const newTestDataBot = require('@jackfranklin/test-data-bot') | ||
import {build, perBuild} from '@jackfranklin/test-data-bot'; | ||
const oldBuilder = legacyTestDataBot.build(...) | ||
// This can be any fake data library you like. | ||
import fake from 'faker'; | ||
const newBuilder = newTestDataBot.build(...) | ||
const userBuilder = build({ | ||
// Within perBuild, call your faker library directly. | ||
name: perBuild(() => fake().name()) | ||
}) | ||
``` | ||
## Breaking changes in the new version | ||
### Node 10.13 is required | ||
Previously Node 8 was supported, but now the minimum is 10.13. | ||
### API for declaring fields has changed | ||
Before: | ||
```js | ||
const userBuilder = build('User').fields({ | ||
name: fake(f => f.name.findName()), | ||
}); | ||
``` | ||
After: | ||
```js | ||
const userBuilder = build('User', { | ||
fields: { | ||
name: fake(f => f.name.findName()), | ||
}, | ||
}); | ||
``` | ||
### `numberBetween` has been removed | ||
`numberBetween` was a shortcut around `fake`. If you need it back you can easily define it: | ||
```js | ||
const { fake } = require('@jackfranklin/test-data-bot'); | ||
const numberBetween = (min, max) => fake(f => f.random.number({ min, max })); | ||
``` | ||
It's highly recommended to maintain a library of custom matchers that are useful for your application. | ||
### `incrementingId` has been removed | ||
You can now call `sequence()` with no argument to get the same result: | ||
Before: | ||
```js | ||
id: incrementingId(); | ||
``` | ||
After: | ||
```js | ||
id: sequence(); | ||
``` | ||
### `arrayOf` has been removed | ||
It was hard to provide a nice API for `arrayOf` that supported all cases. It's now recommended to use the `postBuild` function if you always want an object to have an array of things: | ||
```js | ||
const blogPostBuilder = build('BlogPost', {...}) | ||
const userBuilder = build('User', { | ||
fields: { | ||
name: fake(f => f.name.findName()), | ||
blogPosts: [], | ||
}, | ||
postBuild: user => { | ||
user.blogPosts = Array(3).fill(undefined).map(_ => blogPostBuilder()) | ||
return user | ||
} | ||
}); | ||
``` | ||
### The `map` function has been removed | ||
The old version of test-data-bot provided a map function on each object that it generated. This was confusing and awkward as it meant test-data-bot placed a `map` function on generated objects. If your object had a `map` property, it would be overriden! | ||
This is now replaced by the `postBuild` function. | ||
Before: | ||
```js | ||
const userBuilder = build('User') | ||
.fields({ | ||
name: fake(f => f.name.findName()), | ||
email: sequence(x => `jack${x}@test.com`), | ||
}) | ||
.map(user => ({ | ||
name: user.name.toUpperCase(), | ||
email: user.email.toUpperCase(), | ||
})); | ||
``` | ||
After: | ||
```js | ||
const userBuilder = build('User', { | ||
fields: { | ||
name: fake(f => f.name.findName()), | ||
email: sequence(x => `jack${x}@test.com`), | ||
}, | ||
postBuild: user => ({ | ||
name: user.name.toUpperCase(), | ||
email: user.email.toUpperCase(), | ||
}), | ||
}); | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
30440
208
16
398