Factories for testing JavaScript
When writing tests for Rails apps I tend to use Factory Girl to build the objects that I use in the test suite. Using it means that I don’t have to worry about having to construct objects which pass the model validations, I can just call the factory.
To use Factory Girl, you first create a factory definition. At the most basic level this can be a list of default attributes for a class.
FactoryGirl.define do
factory :post, :class => Post do
# the 'title' attribute is required for all posts
title 'A title'
end
factory :approved_post, :parent => :post do
approved true
end
end
Then, in your test suite, you can call these factories to produce valid instances of the class. You don’t have to worry about manually providing the all attributes required to pass the model validations.
Factory(:post) # => Returns a Post instance with the title already set
Factory(:approved_post) # => Returns a Post instance with the title and approved flag already set
I’ve been wanting something similar for my JavaScript test suites. In particular I want:
- Factories to create specific objects with default values.
- An easy way to define the factories.
- The ability for a factories to inherit their defaults values from another factory. (Similar to
:parent
in Factory Girl)
This is what I came up with.
factory = (originalClass, opts) ->
defaults = _.extend({}, opts.parent?.defaults, opts.defaults)
F = (args = {}) ->
new originalClass(_.extend({}, defaults, args))
F.defaults = defaults
F
A couple of notes:
- It uses the Underscore underscore JavaScript library for the extend call. This is fine for me as underscore is included in the project that I’m working on.
- It assumes that the constructor of the object that you ultimately want returned takes an object eg. a JSON hash of attributes. Again, this is fine for my purposes. It would be trivial to implement some type checking if you were dealing with a class that expected something different.
I like how simple the code is. It’s only basic stuff but it meets the requirements above. What follows is a demonstration of how you would use this.
Consider the following models which have some very basic validations.
M = {} # Namespace for models
M.Vehicle = class Vehicle
constructor: (@attributes={}) ->
if 'registration' not of @attributes
throw 'All vehicles must have a registration number'
get: (attr) -> @attributes[attr]
set: (attr,val) -> @attributes[attr] = val
M.Truck = class Truck extends Vehicle
constructor: (@attributes={}) ->
if 'weightLimit' not of @attributes
throw 'Trucks must have a weight limit'
super
You could define your factories as follows
F = {} # Namespace for factories
F.Vehicle = factory M.Vehicle,
defaults:
registration: 'P717 MLL'
F.MovingVehicle = factory M.Vehicle,
parent: F.Vehicle
defaults:
speed: 70
F.Truck = factory M.Truck,
defaults:
registration: 'R123 YTH'
weightLimit: '17 tonne'
F.BrokenDownTruck = factory M.Truck,
parent: F.Truck
defaults:
speed: 0
engine: 'On fire'
Factories are defined by a call to the factory
function which has
two arguments.
- The constructor function that will produce the object you want
returned (analagous to
:class =>
in Factory Girl). - An options object containing the defaults for the class (the
defaults
property) and an optionalparent
property. Theparent
property is the name of the factory you want to inherit from (analagous to:parent =>
in Factory Girl).
You can then use the factories in your test suite to easily build valid objects.
vehicle = new F.Vehicle
vehicle instanceof M.Vehicle # => true
vehicle.get('registration') # => 'P717 MLL'
movingVehicle = new F.MovingVehicle
movingVehicle instanceof M.Vehicle # => true
movingVehicle.get('speed') # => 70
# The defaults can be overidden when the factory is called
truck = new F.Truck(registration: 'T281 ATB')
truck.get('registration') # => 'T281 ATB'
These are simple examples but they demonstrates the point. Whilst simple, this factory code has really helped keep things slim in situations where I am dealing with lots of models with lots of validations.