DEV Community

Michael Z
Michael Z

Posted on • Edited on • Originally published at michaelzanggl.com

Testing made easy with AdonisJs

Originally posted at michaelzanggl.com. Subscribe to my newsletter to never miss out on new content.

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
Enter fullscreen mode Exit fullscreen mode

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

const aceProviders = [
    '@adonisjs/vow/providers/VowProvider',
]
Enter fullscreen mode Exit fullscreen mode

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 })
  })
}

Enter fullscreen mode Exit fullscreen mode

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 }) => { })
Enter fullscreen mode Exit fullscreen mode

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,
  }
})
Enter fullscreen mode Exit fullscreen mode

Inside the tests, you can now create users easily

const Factory = use('Factory')

Factory.model('App/Models/User').create()
Enter fullscreen mode Exit fullscreen mode

We can also override factory data.

const Factory = use('Factory')

Factory.model('App/Models/User').create({ email: 'email@test.com' })
Enter fullscreen mode Exit fullscreen mode

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()
})
Enter fullscreen mode Exit fullscreen mode

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'))
})
Enter fullscreen mode Exit fullscreen mode

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!


If this article helped you, I have a lot more tips on simplifying writing software here.

Top comments (1)

Collapse
 
charada32 profile image
Mauricio Arantes

That is amazing, and so much helpfull. This post is so hard to find, please, post again in a Adonis Forum, i have im sure you will help tons of devs like me <3