Testing made easy with AdonisJs

Published 8/19/2019

Adonis lets you write really clean tests and is a good candidate for test driven development. I will show how to get started with testing as well as some common techniques regarding the database setup. You can read up about more things in the official docs https://adonisjs.com/docs/4.1/.

Setup

adonis install @adonisjs/vow

Next, add vowProvider under start/app.js in the aceProviders array.

const aceProviders = [
    '@adonisjs/vow/providers/VowProvider',
]

Finally, run adonis test to run all tests.

Dealing with Database

Installing vow creates the two files vowfile.js and .env.testing, as well as an example test.

Test database

Copy over the database settings from your .env to .env.testing. Make sure to change DB_DATABASE to a different database for testing. To make the tests run faster, pick sqlite as the DB_CONNECTION (You need to install the npm dependency sqlite3 for it).

Migrations

If you use migrations, you can easily fill your new testing database before running tests and reset it again after running them.

Simply go inside the file vowfile.js and uncomment all the lines needed for migrations. In essence, this is what the file looks like

'use strict'

const ace = require('@adonisjs/ace')

module.exports = (cli, runner) => {
  runner.before(async () => {
    use('Adonis/Src/Server').listen(process.env.HOST, process.env.PORT)

    await ace.call('migration:run', {}, { silent: true })
  })

  runner.after(async () => {
    use('Adonis/Src/Server').getInstance().close()

    await ace.call('migration:reset', {}, { silent: true })
  })
}

Check out my e-book!

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

Resetting transactions after each test

You don't want a test to accidentally depend on data inserted by a different test. To keep tests simple, it's best to rollback all inserted data after each test. We can do this by using the DatabaseTransactions trait. All queries get wrapped in a transaction that gets rolled back automatically.

Here is an example:

'use strict'

const { test, trait } = use('Test/Suite')('suite name')

trait('DatabaseTransactions')

test('name of test', async ({ assert }) => { })

Model factories

We often rely on data in the database for our tests. It would be quite painful though to always insert the data manually. To make life simple, we can create model factories inside database/factory.js. Here is an example for a user factory:

const Factory = use('Factory')

Factory.blueprint('App/Models/User', (faker, i, data) => {
  return {
    username: faker.username(),
    email: faker.email(),
    password: 'test-password',
    ...data,
  }
})

Inside the tests, you can now create users easily

const Factory = use('Factory')

Factory.model('App/Models/User').create()

We can also override factory data.

const Factory = use('Factory')

Factory.model('App/Models/User').create({ email: '[email protected]' })

Examples

Browser test & Faking emails

'use strict'

const { test, trait } = use('Test/Suite')('ForgotPasswordController')
const Factory = use('Factory')
const Mail = use('Mail')

trait('Test/ApiClient')
trait('DatabaseTransactions')

test('sends forgot password email to user', async ({ assert, client }) => {
  Mail.fake()
  const user = await Factory.model('App/Models/User').create()

  await client.post('/password/forgot').send({ email: user.email }).end()
  const mail = Mail.pullRecent()
  assert.equal(mail.message.to[0].address, user.email)
  assert.equal(mail.message.subject, 'Password Reset')

  Mail.restore()
})

Checking controller response

test('resets password with correct token', async ({ assert, client }) => {
  const user = await Factory.model('App/Models/User').create()
  const token = await (new TokenService).generateToken(user.email)

  const response = await client.post('/password/reset').send({ email: user.email, token, password: 'new password' }).end()
  await user.reload()

  response.assertStatus(200)
  response.assertJSON({ message: 'Password reset successful.' })
  assert.isTrue(await user.verify('new password'))
})

This should give you a good idea on how to get started with testing with the adonis framework. Make sure to read the official docs for more information.

Read more about faking mails, events and dependencies in an ioc container here: https://adonisjs.com/docs/4.1/testing-fakes

Read more about http tests, different request types, authentication, sessions and assertion types here: https://adonisjs.com/docs/4.1/api-tests

Have fun testing and TDDing!