DEV Community

Mike Lowen
Mike Lowen

Posted on • Originally published at mike.lowen.co.nz

Test Data Factories in Javascript

Originally published on my blog.

Friday the 13th was a quiet evening and when the rest of the family were in bed or watching TV I chose to play around with a bit of the thought experiment. This time unlike others I wrote up what I was doing as I went along, what follows is a tidied up and expanded version of the notes that I took. While I was doing this I was primarily using two tools - RunJS for my javascript REPL playground and Typora for my notes.

In the ruby world I'm a fan of factory_bot for generating objects that will serve as test data in my unit tests. I had a bit of time to kill this evening and thought it would be fun to implement the subset of the functionality that I use in factory_bot in javascript to see how I would approach it.

For those unfamiliar with factory_bot is an implementation of the factory pattern to generate objects as alternative to using fixtures in tests. This approach allows you to generate your test data in a known and expected way will allow you to overwrite any properties of the object as appropriate for the test. This approach allows you to remove a bunch of duplicate code from your tests. As an example of how I would use this in my current project I have a front end driven by APIs below is a sample payload from one of the APIs

    {
  "_links": [
    {
      "href": "/clients/898f3b58-6c16-40f7-bf29-c9dac868bb2c",
      "rel": "self"
    },
    {
      "href": "/clients/",
      "rel": "up"
    },
    {
      "href": "/organisations/1f7c161a-8de1-44ff-9ac3-e36ecc01dfdc",
      "rel": "organisation"
    }
  ],
  "_type": "urn:emboss:client",
  "name": "Testing",
  "oauth": {
    "id": "738542d0-e1fb-40c3-8786-8b47119d9151",
    "secret": "************************f8c644399992"
  }
}
Enter fullscreen mode Exit fullscreen mode

Rather than have multiple copies of this object in the codebase each just slightly different to the last, what I'd like to do is have something like the following:

const payload = build(payloadFactory, { name: "Development" });
Enter fullscreen mode Exit fullscreen mode

Where the second argument contains any overrides you want to make to the base object. I've got a bit of a quiet evening so lets play around with this idea, first lets define what we want a factory to look like. Considering that I'm restricting the functionality to only building objects what's wrong with what was defined in the example payload? It's simple and it works, we can assign it to variable call it our factory and pass it into a build function with some overrides to modify some of the properties to create a resulting object. Following that line of thinking the first version of our build function is as simple as:

function build(factory, overrides = {}) {
  return { ...factory, ...overrides };
}
Enter fullscreen mode Exit fullscreen mode

As a generalisation this works great until we want to have more than one instance of our object, the simple solution to that is you overwrite more data to make each unique but you end up finding yourself having to do that a lot, wouldn't it be neat if we could build in some automated uniquness into our factories? To acheive this what I'll do is extend our definition of a factory such that if any property of the factory is an function then it will be called to generate the value of the field in the resulting object. A simple example would be this factory:

const factory = { foo: 1, bar: () => true };
Enter fullscreen mode Exit fullscreen mode

would result in

{ foo: 1, bar: true }
Enter fullscreen mode Exit fullscreen mode

This would mean that should we actually want a property of the resulting object to be a function we'd have to do something like:

const factory = {
  foo: 1,
  bar: () => { 
    return () => console.log("I'm a method on the object!");
  }
}
Enter fullscreen mode Exit fullscreen mode

What would that mean for our build method though? That gets a little more complicated now, first we want to generate the appropriate value for any field, which would take the key of the field, the factory which defines it and finally the overrides the method to do that would look like:

function populate(key, factory, overrides) {
  if (key in overrides) { return overrides[key]; }
  if (factory[key] instanceof Function) { return factory[key](); }

  return factory[key];
}
Enter fullscreen mode Exit fullscreen mode

Next we want to construct the object from the factory based on the properties in the factory like so:

function build(factory, overrides = {}) {
  return Object.keys(factory).reduce((obj, key) => {
    return {
      ...obj,
      [key]: populate(key, factory, overrides)
    };
  }, {});
}
Enter fullscreen mode Exit fullscreen mode

This will do what we want, it will allow you to use a library like Faker to generatedata to populate your object, it may not be the fastest way to do it but I think it's pretty legible. A nice side effect of this approach is this allows our factories to use other factories to define some of their properties, like so:

const factory1 = {
  foo: 1,
  bar: () => true
};

const factory2 = {
  tar: "string",
  baz: () => build(factory1)
}
Enter fullscreen mode Exit fullscreen mode

The next step in complexity for our factories is to introduce dependencies, there are times when you want to populate data in your factory in a specific order, a simple example of this could be a factory like:

const factory = {
  firstName: () => faker.name.firstName(),
  lastName: () => faker.name.lastName(),
  fullName: () => '??'
}
Enter fullscreen mode Exit fullscreen mode

We don't have a way to set it up so that fullName will be populated with firstName + ' ' + lastName, my thoughts around this would be to declare the dependencies on the method like so and pass the in progress object in like so:

const factory = {
  firstName: () => faker.name.firstName(),
  lastName: () => faker.name.lastName(),
  fullName: (obj) => `${obj.firstName} ${obj.lastName}`
}

factory.fullName.dependencies = [ 'firstName', 'lastName' ];
Enter fullscreen mode Exit fullscreen mode

It would also be nice if that was wrapped in a helper function so the factory looks (IMO) cleaner:

const factory = {
  firstName: () => faker.name.firstName(),
  lastName: () => faker.name.lastName(),
  fullName: dependantProperty((obj) => `${obj.firstName} ${obj.lastName}`, [ 'firstName', 'lastName' ])
}
Enter fullscreen mode Exit fullscreen mode

This however breaks our current build function, first we don't pass the object into the populate method and secondly we have no gaurantee on the order in which the properties will be populated. To solve this I'm going to order the keys to populate them in the appropriate order (though I'm not handling circular dependencies). Then to make sure that the population methods have the appropriate context to populate the object so it will now pass the object into the method when populating.

function populate(key, obj, factory, overrides) {
  if (key in overrides) { return overrides[key]; }
  if (factory[key] instanceof Function) { return factory[key](obj); }

  return factory[key];
}

function build(factory, overrides = {}) {
  const order = (prev, current) => {
    if (prev.includes(current)) { return prev; }
    if (factory[current] instanceof Function && factory[current].dependencies) {
      return [ ...factory[current].dependencies.reduce(order, prev), current ];
    }

    return [ ...prev, current];
  }

  return Object.keys(factory).reduce(order, []).reduce((obj, key) => {
    return {
      ...obj,
      [key]: populate(key, obj, factory, overrides)
    };
  }, {});
}
Enter fullscreen mode Exit fullscreen mode

Again I've prioritised readability over performance, this was actually my second attempt my first attempt combined everything into a single function and felt very messy. Lastly I with the build method only populating the resulting object with the fields present in the factory I thought it gives us the oppotunity to pass additional context data into the factory via the overrides parameter and then pass that along to the property population method, allowing you to write population methods like so:

const factory = {
  name: dependantProperty(
    (obj, context) => context.nickName ?? `${obj.firstName} ${obj.lastName}`, 
    [ 'firstName', 'lastName' ]
  ),
  firstName: () => faker.name.firstName(),
  lastName: () => faker.name.lastName(),
}

build(factory, { nickName: 'Bob' });
Enter fullscreen mode Exit fullscreen mode

Another neat aspect of the approach taken to defining factories is that you get extending factories for free by using the spread operator. If we take the above factory and wanted a new factory that extends it to includ an email address we could achieve it like so:

const subfactory = {
  ...factory,
  email: () => faker.internet.email()
};
Enter fullscreen mode Exit fullscreen mode

This was a fun thought experiment about how I could bring some functionality from a library I like in a different language into javascript. It isn't however a 1-1 port of the functionality of factory_bot just the pieces that I found interesting to play around with in an evening. I've setup the code in a git repository if you want to have a look at it in its entirety, given time I may come back and further tune it and publish it to NPM.

Discussion (0)