Skip to content

Latest commit

 

History

History
260 lines (219 loc) · 5.34 KB

README.md

File metadata and controls

260 lines (219 loc) · 5.34 KB

ts-factory

A way to build factories for TypeScript objects

Node.js CI npm GitHub license

Installation

$ npm install ts-factory
$ yarn add ts-factory

buildFactory

buildFactory<T extends object>(defaultObject: T): (overrides?: Partial<T>) => T

buildFactory takes a type parameter and a default object of that type. It returns a function that takes an optional partial object of field overrides.

Example

Consider the following types:

interface Author {
  id?: number;
  name: string;
  email: string;
}

interface Comment {
  id?: number;
  author: Author;
  text: string;
}

interface BlogPost {
  id?: number;
  author: Author;
  title: string;
  body: string;
  comments: Comment[];
}

We can make a factory for Author by calling buildFactory with a default object. Note that optional fields may be omitted.

import { buildFactory } from "ts-factory";

export const buildAuthor = buildFactory<Author>({
  name: "Grant Hutchins",
  email: "[email protected]"
});

We can now use the buildAuthor() function to construct Author instances.

buildAuthor();
// returns {
//   name: "Grant Hutchins",
//   email: "[email protected]"
// }

buildAuthor({});
// returns {
//   name: "Grant Hutchins",
//   email: "[email protected]"
// }

buildAuthor({name: "Mr. Hutchins"});
// returns {
//   name: "Mr. Hutchins",
//   email: "[email protected]"
// }

buildAuthor({id: 1});
// returns {
//   id: 1,
//   name: "Grant Hutchins",
//   email: "[email protected]"
// }

We can now use buildAuthor to make it easier to build a factory for Comment.

import { buildFactory } from "ts-factory";

export const buildComment = buildFactory<Comment>({
  author: buildAuthor(),
  text: "myText"
});

Now we can easily build a Comment, knowing that its Author will also be valid.

buildComment();
// returns {
//   author: {
//     name: "Grant Hutchins",
//     email: "[email protected]"
//   },
//   text: "myText"
// }

buildComment({});
// returns {
//   author: {
//     name: "Grant Hutchins",
//     email: "[email protected]"
//   },
//   text: "myText"
// }

buildComment({id: 1});
// returns {
//   id: 1,
//   author: {
//     name: "Grant Hutchins",
//     email: "[email protected]"
//   },
//   text: "myText"
// }

const anotherAuthor = buildAuthor({
  id: 2,
  name: "Another Author",
  email: "[email protected]",
});

buildComment({author: anotherAuthor});
// returns {
//   author: {
//     id: 2,
//     name: "Another Author",
//     email: "[email protected]"
//   },
//   text: "myText"
// }

We can use buildComment to make it easier to build the default array of comments in buildBlogPost.

export const buildBlogPost = buildFactory<BlogPost>({
  author: buildAuthor(),
  title: "myTitle",
  body: "myBody",
  comments: [buildComment()]
});

Now you can confidently build a BlogPost with as much or as little data as you want.

buildBlogPost();
// returns {
//   author: {
//     name: "Grant Hutchins",
//     email: "[email protected]"
//   },
//   title: "myTitle",
//   body: "myBody",
//   comments: [
//     {
//       author: {
//         name: "Grant Hutchins",
//         email: "[email protected]",
//         text: "myText"
//       }
//     }
//   ]
// }

buildBlogPost({
  id: 3, 
  comments: []
});
// returns {
//   id: 3,
//   author: {
//     name: "Grant Hutchins",
//     email: "[email protected]"
//   },
//   title: "myTitle",
//   body: "myBody",
//   comments: []
// }

buildBlogPost({
  id: 3, 
  author: buildAuthor({id: 4}),
  comments: [
    buildComment({
      id: 5,
      author: buildAuthor({
        id: 6
      })
    }), 
    buildComment({
      author: buildAuthor({
        id: 7
      })
    }), 
  ]
});
// returns {
//   id: 3,
//   author: {
//     id: 4,
//     name: "Grant Hutchins",
//     email: "[email protected]"
//   },
//   title: "myTitle",
//   body: "myBody",
//   comments: [
//     {
//       id: 5,
//       author: {
//         id: 6,
//         name: "Grant Hutchins",
//         email: "[email protected]",
//       },
//       text: "myText"
//     },
//     {
//       author: {
//         id: 7,
//         name: "Grant Hutchins",
//         email: "[email protected]",
//       },
//       text: "myText"
//     }
//   ]
// }

FAQs

Isn't this just currying one argument to Object.assign?

Yes.

Why can't I just do that myself?

You can.

Then what's the point?

The parameterized type argument helps guide you to get the types of the default objects and the overrides correct. It's not much, but it does help a small bit.

OK, how does it help?

One example: if you add a required field to an interface that is heavily used throughout your test suite, you can just go in and add a value for this new field to its factory's default object and now all of your tests that use the factory will compile.