Skip to content

KensioSoftware/part-factory

Repository files navigation

Part Factory logo  Part Factory test entity factory pattern

npm version CI Coverage Node TypeScript License

Part Factory is a minimalist object factory pattern with strong typing. Create object factories with suitable default value generation, then override the defaults as needed per test case.

https://partfactory.dev/

Installation

npm install @kensio/part-factory

Usage

Suppose we have an object with this structure, and we want to generate valid instances for testing:

interface Foo {
  name: string;
  size: number;
}

StaticFactory

The simplest object factory has static default values which can be overridden when making objects.

import { StaticFactory } from "@kensio/part-factory";

const fooFactory = new StaticFactory<Foo>({
  name: "Foobar",
  size: 10,
});

const defaultFoo = fooFactory.make();
// { name: "Foobar", size: 10 }

const myFoo = fooFactory.make({ size: 20 });
// { name: "Foobar", size: 20 }

The types in Part Factory are detailed enough for TypeScript to enforce strong typing on the factories, object structures and overrides.

DynamicFactory

We might want to generate dynamic values for each new object. This works well with a data generation library such as Faker.

import { DynamicFactory } from "@kensio/part-factory";
import { faker } from "@faker-js/faker";

const fooFactory = new DynamicFactory<Foo>(() => ({
  name: faker.word.noun(),
  size: faker.number.int({ max: 100 }),
}));

const defaultFoo = fooFactory.make();
// { name: "external", size: 42 }

const myFoo = fooFactory.make({ size: 20 });
// { name: "front", size: 20 }

VariantFactory

We can create variant factories that apply preset variations to objects made by a base factory.

import { DynamicFactory, VariantFactory } from "@kensio/part-factory";
import { faker } from "@faker-js/faker";

const animalFactory = new DynamicFactory<Foo>(() => ({
  name: faker.animal.type(),
  size: faker.number.int({ max: 100 }),
}));

const zebraFactory = new VariantFactory<Foo>(baseFactory, {
  name: "Zebra",
});

const zebra = zebraFactory.make();
// { name: "Zebra", size: 42 }

const largeZebra = zebraFactory.make({ size: 100 });
// { name: "Zebra", size: 100 }

MappedFactory

Sometimes the values you want to override are easier to represent as a structured input object, but the value you want from the factory has a different output type.

MappedFactory generates the input object, applies overrides to that input object, then maps the completed input into the final output.

import { MappedFactory } from "@kensio/part-factory";

interface ArnComponents {
  partition: "aws";
  service: string;
  region: string;
  accountId: string;
  resourceType: string;
  resourceId: string;
}

type Arn = `arn:aws:${string}:${string}:${string}:${string}/${string}`;

const arnFactory = new MappedFactory<ArnComponents, Arn>(
  () => ({
    partition: "aws",
    service: "lambda",
    region: "eu-west-1",
    accountId: "123456789012",
    resourceType: "function",
    resourceId: "example-function",
  }),
  (components) =>
    `arn:${components.partition}:${components.service}:${components.region}:${components.accountId}:${components.resourceType}/${components.resourceId}`,
);

const defaultArn = arnFactory.make();
// "arn:aws:lambda:eu-west-1:123456789012:function/example-function"

const bucketArn = arnFactory.make({
  service: "s3",
  resourceType: "bucket",
  resourceId: "abc123def4",
});
// "arn:aws:s3:eu-west-1:123456789012:bucket/abc123def4"

Nested structures

The overrides are partial down through the object structure. This means that we can override one nested property without replacing the entire tree to reach that property.

import { StaticFactory } from "@kensio/part-factory";

interface UserProfile {
  username: string;
  contact: {
    email: string;
    phone: string;
    address: {
      line1: string;
      city: string;
      country: string;
    };
  };
}

const userProfileFactory = new StaticFactory<UserProfile>({
  username: "foo_user",
  contact: {
    email: "foo@example.com",
    phone: "123456789",
    address: {
      line1: "1 Foo Street",
      city: "London",
      country: "UK",
    },
  },
});

const userProfile = userProfileFactory.make({
  contact: {
    address: {
      city: "Manchester",
    },
  },
});

// {
//   username: "foo_user",
//   contact: {
//     email: "foo@example.com",
//     phone: "123456789",
//     address: {
//       line1: "1 Foo Street",
//       city: "Manchester",
//       country: "UK",
//     },
//   },
// }

About

Partial object factory pattern for testing @kensio/part-factory

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Contributors