Adonis.js - Advanced factories

Published 2/10/2020

For a while now I have been using these little tricks to simplify writing factories.

Nr. 1: override

Oftentimes you write a factory that requires a foreign key from another table like here:

Factory.blueprint('App/Models/Subscription', async (faker, i, data) => {
  return {
    user_id: await Factory.model('App/Models/User').create().then(user => user.id),
    plan: 'monthly',
  }
})

But sometimes you already have a user and don't want to create a new one, so you need to add some logic to your blueprint

Factory.blueprint('App/Models/Subscription', async (faker, i, data = {}) => {
  return {
    user_id: data.user_id || await Factory.model('App/Models/User').create().then(user => user.id),
    plan: 'monthly',
  }
})

and then call it like this

Factory.model('App/Models/Subscription', { user_id: 1 })

Having to do this repeatedly can get quite cumbersome, because you have to write a lot of it for your tests. I have created this little "magic" method that automates this for you: https://github.com/MZanggl/adonis-override-factory.

Our blueprint from before now becomes

Factory.blueprint('App/Models/Subscription', async (faker, i, data) => {
  return override({
    user_id: () => Factory.model('App/Models/User').create(),
    plan: 'monthly',
  }, data)
})

Note how the default data is now wrapped inside the "override" method. The "Factory...create()" is also wrapped in a higher-order function to avoid executing it when data was passed.

Finally, there is no need for .then(user => user.id) because the "override" method resolves the id automatically when it receives an object.

Check out my e-book!

Learn to simplify day-to-day code and the balance between over- and under-engineering.

Nr 2: 'Factory.model' => 'factory'

Inside vowfile.js where you set up the test environment I have added this little global helper:

const Factory = use('Factory')

global.factory = (model) => {
    return Factory.model(model)
}

So instead of writing

const Factory = use('Factory')
Factory.model('App/Models/User').create()

we can now do

factory('App/Models/User').create()

It's again a way to save some keystrokes. While globals are considered harmful, there are some use cases and there is no problem to use globals when the price is right.


I like to make not too many modifications to the framework as it can make upgrading more difficult and the project becomes more custom in general, so I try to keep such things to a minimum and only apply them after a long trial period with the things the framework provides by default.