Skip to content

Commit

Permalink
feat(*): v2 of Factoryse
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Completely new API and integration.
  • Loading branch information
thekeogh committed Jan 3, 2024
1 parent e64ba8e commit a49b7bd
Show file tree
Hide file tree
Showing 43 changed files with 4,274 additions and 1,236 deletions.
36 changes: 31 additions & 5 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
}],
"sort-imports": ["error", {
"ignoreDeclarationSort": true,
"allowSeparatedGroups": true
"allowSeparatedGroups": true,
"ignoreMemberSort": false
}],
"@typescript-eslint/no-unused-vars": ["warn"],
"@typescript-eslint/no-explicit-any": "off",
Expand All @@ -33,12 +34,39 @@
"group": "unknown",
"position": "before"
},
{
"pattern": "@types",
"group": "unknown",
"position": "before"
},
{
"pattern": "@core/**",
"group": "unknown",
"position": "before"
},
// ----
{
"pattern": "@utils",
"pattern": "@utils/**",
"group": "unknown"
},
// ----
{
"pattern": "@factory",
"group": "unknown",
"position": "after"
},
// ----
{
"pattern": "@factory/**",
"group": "internal"
},
// ----
{
"pattern": "@tests/**",
"group": "internal",
"position": "after"
}
// ----
],
"pathGroupsExcludedImportTypes": ["builtin"],
"groups": [
Expand All @@ -61,9 +89,7 @@
"files": ["tests/**"],
"rules": {
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": ["error", {
"ts-ignore": "allow-with-description"
}]
"@typescript-eslint/ban-ts-comment": "off"
}
}]
}
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
pull_request:

env:
NODE_VERSION: "18.18"
NODE_VERSION: "16.20.2"

jobs:
lint:
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.18
lts/*
6 changes: 1 addition & 5 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@ package-lock.json
dist/
.github/

# Testing
coverage/

# Config
tsconfig.*
.eslintrc

# Misc
*.md
.vscode/*
*.md
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"javascript.preferences.importModuleSpecifierEnding": "js",
"typescript.preferences.importModuleSpecifierEnding": "js"
}
209 changes: 95 additions & 114 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,173 +1,154 @@
# Factoryse

[![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
[![build](https://github.com/thekeogh/factoryse/actions/workflows/release.yml/badge.svg)](https://github.com/thekeogh/factoryse/actions)[![semantic-release: npm](https://img.shields.io/badge/semantic--release-npm-CB0000?logo=semantic-release)](https://github.com/semantic-release/semantic-release)[![node.js: >=16.20.2](https://img.shields.io/badge/node.js-%3E=16.20.2-036E02?logo=node.js)](https://nodejs.org)

Factoryse is a test factory library built with [TypeScript](https://www.typescriptlang.org/), designed to streamline the creation of test data for your [Node.js](https://nodejs.org/) applications. Craft and manage test objects effortlessly, optimising the efficiency of your testing workflow. This project draws inspiration from Ruby's renowned [Factory Bot](https://github.com/thoughtbot/factory_bot), bringing similar ease and consistency to the world of TypeScript and Node.js testing.

Factoryse is compatible with any modern testing suite, including [Vitest](https://vitest.dev/) and [Jest](https://jestjs.io/), without being tied to any specific one.

----
Factoryse is designed to streamline the creation of test data, offering a simple and efficient approach to generate random (or deterministic, if preferred) data, thereby reducing redundancy in your testing setup.

**Factoryse is currently in active development. Comprehensive details will be provided here in the near future.**

----
Factoryse is compatible with any modern testing suite, including [Vitest](https://vitest.dev/) and [Jest](https://jestjs.io/), without being tied to any specific one.

## Install

```shell
npm install --save-dev factoryse
```

## Usage
## How to use

### Initialise
### Defining a factory

To begin, initialise your factory with the initial data for your schema:
First, define your factory structure. This involves specifying the data model and creating the factory instance.

```typescript
import { Factory } from "factoryse";

// Define the User shape
interface User {
name: string;
tags: string[];
address: {
line1: string;
}
};

const user = new Factory<User>({
name: "Jon Jones",
tags: ["male", "manager"],
address: {
line1: "66 Richmond Road",
},
});
email: string;
createdAt?: Date;
}

// Create the user factory
const user = new Factory<User>(faker => ({
email: faker.internet.email()
}));
```

> The specification of the type `<User>` during Factory initialisation is entirely optional.
> It's recommended to use [faker](https://fakerjs.dev/) during factory instantiation for generating diverse random data.
### `spawn(count: number, data: (faker: Faker) => T)`
### Generating entries

To create multiple `Factory` instances simultaneously, you can utilise the `spawn` helper method, which generates an array of `Factory` instances:
Generate a single entry:

```typescript
import { spawn } from "factoryse";

// Spawn an array of 5 Factory instances
const users: Factory<User>[] = spawn(5, faker => ({
name: faker.person.fullName(),
}));
const factory = user.make();
// Generates: [{ email: "Jo65@gmail.com" }]
```

When initialising your objects, you have the flexibility to use the [Faker](https://github.com/faker-js/faker) closure to easily generate random data. However, if you prefer, you can provide static strings or values instead of random data.
To generate multiple entries:

### `add(key: string, value: any)`
```typescript
const factory = user.make(2);
// Generates: [{ email: "Jo65@gmail.com" }, { email: "Alv38@hotmail.com" }]
```

This method adds a new key-value pair to the object. You have the flexibility to use dot notation for nesting keys within your schema:
### Retrieving entries

Leverage the `get()` action to access entries created by your Factory. Factoryse offers a variety of query methods to facilitate this retrieval process.

```typescript
// Adding a top-level key
factory.add("foo", "bar");
// Retrieve all entries
users.get().all(); // User[]

// Adding a nested key
factory.add("foo.bar", "baz")
```
// Retrieve the first entry or the first (`n: number`) entries
users.get().first(); // User[]
users.get().first(n); // User[]

For the sake of maintaining test stability, Factoryse will raise an error if the specified key already exists within the schema. To update existing values in your schema, it's advisable to use the `modify()` or `assign()` methods.
// Retrieve the last entry or the last (`n: number`) entries
users.get().last(); // User[]
users.get().last(n); // User[]

> Please note that Factoryse doesn't strictly adhere to the specified interface (e.g., `User`) and allows the addition of keys and values with any name and type to the object. This flexibility enables you to test scenarios with incorrect value types and additional keys that aren't defined in the interface.
// Retrieve a random entry or multiple random (`n: number`) entries
users.get().any(); // User[]
users.get().any(n); // User[]

### `modify(key: string, value: any)`
// Retrieve a single entry by its index (`n: number`)
users.get().at(n); // User

This method modifies a value in the object. You have the flexibility to use dot notation for modifying nested keys within your schema:
// Retrieve multiple entries by their index (`n: number[]`)
users.get().pick(n); // User[]

```typescript
// Modify a top-level key
factory.modify("foo", "bar");
// Retrieve multiple entries between indexes (`start: number, end: number`)
users.get().between(start, end); // User[]

// Modifying a nested key
factory.modify("foo.bar", "baz")
// Retrieve multiple entries by criteria (`c: Partial<User>`)
users.get().by(c); // User[]
```

For the sake of maintaining test stability, Factoryse will raise an error if the specified key does not exist within the schema. To add new values to your schema, it's advisable to use the `add()` or `assign()` methods.
### Editing entries

> It's important to be aware that Factoryse doesn't strictly adhere to the specified interface (e.g., `User`) and provides flexibility in modifying values of different types than what the interface demands. This flexibility allows you to conduct tests involving scenarios with incorrect value types and the addition of keys that aren't originally defined in the interface.
Leverage the `set(source)` action to edit entries in your Factory. Factoryse offers a variety of query methods to facilitate this editing process.

### `remove(key: string)`
```typescript
// Closure to apply modifications to entries
const source = faker => ({ createdAt: faker.date.recent() })

This method removes key-value pairs from the object. You have the flexibility to use dot notation for removing nested keys within your schema:
// Update all entries
users.set(source).all(); // User[]

```typescript
// Remove a top-level key
factory.remove("foo");
// Update the first entry or the first (`n: number`) entries
users.set(source).first(); // User[]
users.set(source).first(n); // User[]

// Removing a nested key
factory.remove("foo.bar")
```
// Update the last entry or the last (`n: number`) entries
users.set(source).last(); // User[]
users.set(source).last(n); // User[]

For the sake of maintaining test stability, Factoryse will raise an error if the specified key does not exist within the schema.
// Update a random entry or multiple random (`n: number`) entries
users.set(source).any(); // User[]
users.set(source).any(n); // User[]

### `assign(source: Record<string, any>, mergeArrays: boolean = false)`
// Update a single entry by its index (`n: number`)
users.set(source).at(n); // User

This method facilitates the merging of the source schema with your object, with the source schema taking precedence over the target object.
// Update multiple entries by their index (`n: number[]`)
users.set(source).pick(n); // User[]

```typescript
// Merge a source object into your target
factory.assign({
age: 42,
tags: ["boss"],
address: {
line1: "23 Derton Road, London",
country: "GB"
}
});
```
// Update multiple entries between indexes (`start: number, end: number`)
users.set(source).between(start, end); // User[]

When `mergeArrays` is set to `false` (the default), arrays will not be merged; instead, the source array will overwrite the target array. In this case, the result would be:

```json
{
"name": "Jon Jones",
"age": 42,
"tags": ["boss"],
"address": {
"line1": "23 Derton Road, London",
"country": "GB"
},
}
// Update multiple entries by criteria (`c: Partial<User>`)
users.set(source).by(c); // User[]
```

If `mergeArrays` was set to `true`, the result would be:

```json
{
"name": "Jon Jones",
"age": 42,
"tags": ["male", "manager", "boss"],
"address": {
"line1": "23 Derton Road, London",
"country": "GB"
},
}
```
### Deleting entries

> It's worth noting that Factoryse offers flexibility by not strictly adhering to the specified interface (e.g., `User`). This flexibility allows you to test scenarios with different value types and the addition of keys not originally defined in the interface.
Leverage the `delete()` action to delete entries from your Factory. Factoryse offers a variety of query methods to facilitate this deleting process.

### `reset()`
```typescript
// Delete all entries
users.delete().all(); // User[]

This method restores the target object to its initial state as defined during instantiation. Be cautious as this action permanently erases any changes made to the object since its creation.
// Delete the first entry or the first (`n: number`) entries
users.delete().first(); // User[]
users.delete().first(n); // User[]

```typescript
// Reset the target back to its original state
factory.reset();
```
// Delete the last entry or the last (`n: number`) entries
users.delete().last(); // User[]
users.delete().last(n); // User[]

### `get()`
// Delete a random entry or multiple random (`n: number`) entries
users.delete().any(); // User[]
users.delete().any(n); // User[]

This method provides you with the resulting schema object after all modifications have been applied.
// Delete a single entry by its index (`n: number`)
users.delete().at(n); // User

```typescript
// Retrieve the current schema
factory.get()
```
// Delete multiple entries by their index (`n: number[]`)
users.delete().pick(n); // User[]

// Delete multiple entries between indexes (`start: number, end: number`)
users.delete().between(start, end); // User[]

// Delete multiple entries by criteria (`c: Partial<User>`)
users.delete().by(c); // User[]
```
Loading

0 comments on commit a49b7bd

Please sign in to comment.