A way to build factories for TypeScript objects
$ npm install ts-factory
$ yarn add ts-factory
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.
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"
// }
// ]
// }
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.