TDD course with AdonisJs - 3. Model factories & DB transactions

michi

Michael Z

Posted on September 9, 2019

TDD course with AdonisJs - 3. Model factories & DB transactions

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

Welcome back! Let's get right into our second test, Deleting threads!

You can find all the changes we make throughout this post here: https://github.com/MZanggl/tdd-adonisjs/commit/95a52a79de271c126a3a1e0a8e087fb87d040555

Now in order to delete a thread, we first have to create a thread.
For now let's just do this manually in the test, but in the end, we are going to refactor this again!

Add a new test inside thread.spec.js

test('can delete threads', async ({ assert, client }) => {
  const thread = await Thread.create({
    title: 'test title',
    body: 'test body',
  })

  const response = await client.delete(`threads/${thread.id}`).send().end()
  console.log(response.error)
  response.assertStatus(204)
})
Enter fullscreen mode Exit fullscreen mode

Run it! We receive a 404, since we have not created the route yet, so let's add it to our resourceful route in routes.js. By convention the action to delete an entity is destroy.

// start/routes.js

Route.resource('threads', 'ThreadController').only(['store', 'destroy'])
Enter fullscreen mode Exit fullscreen mode

We now get the error RuntimeException: E_UNDEFINED_METHOD: Method destroy missing, so let's create the method in our ThreadController.

async destroy({ params }) {
    const thread = await Thread.findOrFail(params.id)
    await thread.delete()
}
Enter fullscreen mode Exit fullscreen mode

The test passes! But now let's make sure it was actually deleted from the database. Head over to the test and add the following check at the end of our test.

assert.equal(await Thread.getCount(), 0)
Enter fullscreen mode Exit fullscreen mode

Whoops!

1. can delete threads
  expected 1 to equal 0
  1 => 0
Enter fullscreen mode Exit fullscreen mode

How did that happen, we are deleting it right?

Let's try running only the "can delete threads" test and see what happens

npm t -- -g 'can delete threads'
Enter fullscreen mode Exit fullscreen mode

or alternatively

adonis test -g 'can delete threads'
Enter fullscreen mode Exit fullscreen mode

It passes, right?

It makes sense, since we never deleted the inserted thread from the first test. To fix this we simply have to load another trait at the top of the test.

trait('DatabaseTransactions')
Enter fullscreen mode Exit fullscreen mode

This will wrap all queries in a transaction that gets rolled back after each test, so when our second test runs, the thread from the first test is long rolled back. Give the test suite a run!

Refactoring

Okay, there is quite a lot to refactor in our test.

Let's first look at these lines

const thread = await Thread.create({
    title: 'test title',
    body: 'test body',
  })
Enter fullscreen mode Exit fullscreen mode

The more tests we need, the more tedious this becomes. Luckily Adonis allows to create model factories. For this, head over to database/factory.js and add the following code.

Factory.blueprint('App/Models/Thread', (faker) => {
  return {
    title: faker.word(),
    body: faker.paragraph(),
  }
})
Enter fullscreen mode Exit fullscreen mode

Also uncomment const Factory = use('Factory') at the top of the file.

faker is an instance of https://chancejs.com, check out their documentation for all the things you can fake.

Now back in our test we can replace the manual thread creation with simply

const thread = await Factory.model('App/Models/Thread').create()
Enter fullscreen mode Exit fullscreen mode

Also, add const Factory = use('Factory') to the top of the test.

Run the tests and you should still get green!


There is also a nicer way of doing

const response = await client.delete(`threads/${thread.id}`).send().end()
Enter fullscreen mode Exit fullscreen mode

In particular threads/${thread.id}.
It would be more elegant if we were able to do const response = await client.delete(thread.url()).send().end(), in fact let's just do that and run the test. It will complain that thread.url is not a function.

For this to work, we have to add the method url to our threads model. But currently, we are inside an integration test. So how can we do this the TDD way?

The solution is to break down from the feature test into a unit test for our Thread model.

Let's create a test using adonis make:test Thread and this time choose unit.

This is what the unit test will look like

'use strict'

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

trait('DatabaseTransactions')

test('can access url', async ({ assert }) => {
  const thread = await Factory.model('App/Models/Thread').create()
  assert.equal(thread.url(), `threads/${thread.id}`)
})
Enter fullscreen mode Exit fullscreen mode

Nicely throwing the same error TypeError: thread.url is not a function.
Remember how I said TDD follows the concept red -> green -> refactor. What I did not mention before, but what we just learned, is that these three steps are happening in a loop!

Head over to app/Models/Thread.js and add the following method to the Thread class

url() {
    return `threads/${this.id}`
}
Enter fullscreen mode Exit fullscreen mode

Run the test again, this time both the unit and the functional test should be green!

Now we can already create and delete threads, but so far, even guests can perform these actions. Next time let's see how we can restrict these actions to only authenticated users and add a user_id field to our threads table.

💖 💪 🙅 🚩
michi
Michael Z

Posted on September 9, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related