TDD course with AdonisJs - 3. Model factories & DB transactions
Published 9/9/2019
This article is part of a series:
1 TDD course with AdonisJs - 1. Let's build a reddit clone
2 TDD course with AdonisJs - 2. Our first test
3 TDD course with AdonisJs - 3. Model factories & DB transactions
4 TDD course with AdonisJs - 4. Using the auth middleware
5 TDD course with AdonisJs - 5. Middlewares
6 TDD course with AdonisJs - 6. Validation
7 TDD course with AdonisJs - 7. Moderators
8 TDD course with AdonisJs - 8. Third party APIs, ioc and custom validators
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)
})
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'])
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()
}
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)
Whoops!
1. can delete threads
expected 1 to equal 0
1 => 0
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'
or alternatively
adonis test -g 'can delete threads'
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')
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!
Check out my e-book!
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',
})
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(),
}
})
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()
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()
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}`)
})
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}`
}
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.