与AdonisJs的TDD价格 – 4.使用auth中间件
我们的路线目前可以由未经过身份验证的用户访问,所以让我们编写一个新的测试来确认这一点
与往常一样,您可以在GitHub上的以下提交中找到我们所做的所有更改:https://github.com/MZanggl/tdd-adonisjs/commit/6f50e5f277674dfe460b692cedc28d5a67d1cc55
// test/functional/thread.spec.js test('unauthenticated user cannot create threads', async ({ client }) => { const response = await client.post('/threads').send({ title: 'test title', body: 'body', }).end() response.assertStatus(401) })
测试失败,因为响应代码仍然是200.所以让我们将集成的auth中间件添加到我们的路由中。
// start/routes.js Route.resource('threads', 'ThreadController').only(('store', 'destroy')).middleware('auth')
这使测试通过,但同时,我们打破了其他测试,因为他们现在也返回状态代码401(未经验证)。
为了使它们再次通过,我们需要能够在测试中与用户进行身份验证。
首先,让我们为用户创建一个模型工厂,就像我们使用线程一样。
回头去 database/factory.js
并为用户添加以下蓝图。
Factory.blueprint('App/Models/User', (faker) => { return { username: faker.username(), email: faker.email(), password: '123456', } })
让我们在我们的函数thread.spec.js测试中尝试一下我们可以使用“登录” loginVia
方法。
test('can create threads', async ({ client }) => { const user = await Factory.model('App/Models/User').create() const response = await client.post('/threads').loginVia(user).send({ title: 'test title', body: 'body', }).end() response.assertStatus(200) const thread = await Thread.firstOrFail() response.assertJSON({ thread: thread.toJSON() }) })
但是,这会因错误而失败 ...loginVia is not a function
。像以前一样,特质可以帮助我们解决这个问题,所以让我们补充一下 trait('Auth/Client')
到文件的顶部并再次运行测试。
甜让我们对现有的失败删除测试应用相同的修复。
test('can delete threads', async ({ assert, client }) => { const user = await Factory.model('App/Models/User').create() const thread = await Factory.model('App/Models/Thread').create() const response = await client.delete(thread.url()).send().loginVia(user).end() response.assertStatus(204) assert.equal(await Thread.getCount(), 0) })
当然,任何用户都可以删除任何线程并不是最佳的,但我们正在实现这一目标……
我认为现在是时候将测试用例重命名为更有意义的东西了。
test('可以创建线程')=> test('授权用户可以创建线程')
test('可以删除线程')=> test('授权用户可以删除线程')
完成此操作后,将user_id列添加到线程表是有意义的。
为此,我们首先必须重构我们的测试用例'授权用户可以创建线程'。我们目前没有测试标题和正文是否正确插入,我们只是断言响应与数据库中找到的第一个线程相匹配。所以我们也要添加这个部分
test('authorized user can create threads', async ({ client }) => { const user = await Factory.model('App/Models/User').create() const attributes = { title: 'test title', body: 'body', } const response = await client.post('/threads').loginVia(user).send(attributes).end() response.assertStatus(200) const thread = await Thread.firstOrFail() response.assertJSON({ thread: thread.toJSON() }) response.assertJSONSubset({ thread: attributes }) })
测试仍然应该通过,但是让我们继续将user_id添加到我们添加的断言中
response.assertJSONSubset({ thread: {...attributes, user_id: user.id} })
我们现在收到错误
expected { Object (thread) } to contain subset { Object (thread) } { thread: { - created_at: "2019-09-08 08:57:59" - id: 1 - updated_at: "2019-09-08 08:57:59" + user_id: 1 }
所以让我们转到ThreadController并用这个替换“store”方法
async store({ request, auth, response }) { const attributes = { ...request.only(('title', 'body')), user_id: auth.user.id } const thread = await Thread.create(attributes) return response.json({ thread }) }
不用担心,我们会在测试结果为绿色后重构。
现在测试将在断言时失败 response.assertStatus(200)
有500个错误代码,所以让我们添加 console.log(response.error)
在上一行。它将揭示我们的表缺少列 user_id
。
转到线程迁移文件并在正文之后,像这样添加user_id列
table.integer('user_id').unsigned().notNullable()
我们还用外键注册新列。我想在所有列声明后保留外键。
// ... column declarations table.foreign('user_id').references('id').inTable('users')
太棒了,这个测试又过去了
但事实证明我们打破了另外两个测试
我们的单元测试“可以访问url”,功能测试“授权用户可以删除线程”现在因为失败而失败 SQLITE_CONSTRAINT: NOT NULL constraint failed: threads.user_id
。
这两个测试都使用我们的模型工厂来处理线程,当然我们还没有使用用户ID更新它。所以让我们来看看吧 database/factory.js
并将user_id添加到线程工厂,如下所示:
return { title: faker.word(), body: faker.paragraph(), user_id: (await Factory.model('App/Models/User').create()).id }
务必将功能转换为 async
功能,因为我们必须在这里使用等待。
如果我们再次运行我们的测试套件,我们应该变绿
重构
让我们转到ThreadController并想一下这个部分更面向对象的方法:
const attributes = { ...request.only(('title', 'body')), user_id: auth.user.id } const thread = await Thread.create(attributes)
如果我们不必自己定义关系,那就太好了。
我们可以用这个换掉这两行
const thread = await auth.user.threads().create(request.only(('title', 'body')))
由于我们还没有定义关系,我们将得到错误 TypeError: auth.user.threads is not a function
。
所以我们要做的就是转到“App / Models / User.js”并添加关系
threads() { return this.hasMany('App/Models/Thread') }
就是这样,一个坚实的重构
让我们快速添加另一个测试,以确保未经身份验证的用户无法删除线程
test('unauthenticated user can not delete threads', async ({ assert, client }) => { const thread = await Factory.model('App/Models/Thread').create() const response = await client.delete(thread.url()).send().end() response.assertStatus(401) })
当然,我们必须在这里添加更多测试,并非每个用户都应该能够简单地删除任何线程。下一次,让我们测试并创建一个为我们解决这个问题的策略