Skip to content

HTTP 测试

HTTP 测试是指通过对应用程序端点发出实际的 HTTP 请求并断言响应体、头、cookie、会话等来测试它们。

HTTP 测试使用 Japa 的 API 客户端插件执行。API 客户端插件是一个无状态请求库,类似于 Axiosfetch,但更适合测试。

如果您想在真实浏览器中测试 Web 应用程序并以编程方式与它们交互,我们建议使用使用 Playwright 进行测试的浏览器客户端

设置

第一步是从 npm 包注册表安装以下包。

:::codegroup

sh
// title: npm
npm i -D @japa/api-client

:::

注册插件

在继续之前,请在 tests/bootstrap.ts 文件中注册插件。

ts
// title: tests/bootstrap.ts
import { apiClient } from '@japa/api-client'

export const plugins: Config['plugins'] = [
  assert(),
  // highlight-start
  apiClient(),
  // highlight-end
  pluginAdonisJS(app),
]

apiClient 方法可选地接受服务器的 baseURL。如果未提供,它将使用 HOSTPORT 环境变量。

ts
import env from '#start/env'

export const plugins: Config['plugins'] = [
  apiClient({
    baseURL: `http://${env.get('HOST')}:${env.get('PORT')}`
  })
]

基本示例

注册 apiClient 插件后,您可以从 TestContext 访问 client 对象来发出 HTTP 请求。

HTTP 测试必须写在为 functional 测试套件配置的文件夹中。您可以使用以下命令创建新的测试文件。

sh
node ace make:test users/list --suite=functional
ts
import { test } from '@japa/runner'

test.group('Users list', () => {
  test('获取用户列表', async ({ client }) => {
    const response = await client.get('/users')

    response.assertStatus(200)
    response.assertBody({
      data: [
        {
          id: 1,
          email: 'foo@bar.com',
        }
      ]
    })
  })
})

要查看所有可用的请求和断言方法,请确保阅读 Japa 文档

Open API 测试

断言和 API 客户端插件允许您使用 Open API 规范文件编写断言。您可以使用规范文件来测试 HTTP 响应的形状,而不是手动针对固定负载测试响应。

这是保持 Open API 规范和服务器响应同步的好方法。因为如果您从规范文件中删除某个端点或更改响应数据形状,您的测试将失败。

注册模式

AdonisJS 不提供从代码生成 Open API 模式文件的工具。您可以手动编写它或使用图形工具来创建它。

一旦您有了规范文件,将其保存在 resources 目录中(如果缺少则创建该目录),并在 tests/bootstrap.ts 文件中使用 openapi-assertions 插件注册它。

sh
npm i -D @japa/openapi-assertions
ts
// title: tests/bootstrap.ts
import app from '@adonisjs/core/services/app'
// highlight-start
import { openapi } from '@japa/openapi-assertions'
// highlight-end

export const plugins: Config['plugins'] = [
  assert(),
  // highlight-start
  openapi({
    schemas: [app.makePath('resources/open_api_schema.yaml')]
  })
  // highlight-end
  apiClient(),
  pluginAdonisJS(app)
]

编写断言

注册模式后,您可以使用 response.assertAgainstApiSpec 方法对 API 规范进行断言。

ts
test('分页帖子', async ({ client }) => {
  const response = await client.get('/posts')
  response.assertAgainstApiSpec()
})
  • response.assertAgainstApiSpec 方法将使用请求方法端点响应状态码来查找预期的响应模式。
  • 当无法找到响应模式时将引发异常。否则,将根据模式验证响应体。

只测试响应的形状,而不是实际值。因此,您可能需要编写额外的断言。例如:

ts
// 断言响应符合模式
response.assertAgainstApiSpec()

// 断言预期值
response.assertBodyContains({
  data: [{ title: 'Adonis 101' }, { title: 'Lucid 101' }]
})

您可以在 HTTP 请求期间使用 withCookie 方法发送 cookie。该方法接受 cookie 名称作为第一个参数,值作为第二个参数。

ts
await client
  .get('/users')
  .withCookie('user_preferences', { limit: 10 })

withCookie 方法定义一个签名 cookie。此外,您可以使用 withEncryptedCookiewithPlainCookie 方法向服务器发送其他类型的 cookie。

ts
await client
  .get('/users')
  .withEncryptedCookie('user_preferences', { limit: 10 })
ts
await client
  .get('/users')
  .withPlainCookie('user_preferences', { limit: 10 })

您可以使用 response.cookies 方法访问由 AdonisJS 服务器设置的 cookie。该方法将 cookie 作为键值对对象返回。

ts
const response = await client.get('/users')
console.log(response.cookies())

您可以使用 response.cookie 方法按名称访问单个 cookie 值。或使用 assertCookie 方法断言 cookie 值。

ts
const response = await client.get('/users')

console.log(response.cookie('user_preferences'))

response.assertCookie('user_preferences')

填充会话存储

如果您使用 @adonisjs/session 包在应用程序中读取/写入会话数据,您可能还需要使用 sessionApiClient 插件在编写测试时填充会话存储。

设置

第一步是在 tests/bootstrap.ts 文件中注册插件。

ts
// title: tests/bootstrap.ts
// insert-start
import { sessionApiClient } from '@adonisjs/session/plugins/api_client'
// insert-end

export const plugins: Config['plugins'] = [
  assert(),
  pluginAdonisJS(app),
  // insert-start
  sessionApiClient(app)
  // insert-end
]

然后,更新 .env.test 文件(如果缺少则创建一个)并将 SESSON_DRIVER 设置为 memory

dotenv
// title: .env.test
SESSION_DRIVER=memory

使用会话数据发出请求

您可以使用 Japa API 客户端上的 withSession 方法使用一些预定义的会话数据发出 HTTP 请求。

withSession 方法将创建一个新的会话 ID 并使用数据填充内存存储,您的 AdonisJS 应用程序代码可以像往常一样读取会话数据。

请求完成后,会话 ID 及其数据将被销毁。

ts
test('使用购物车商品结账', async ({ client }) => {
  await client
    .post('/checkout')
    // highlight-start
    .withSession({
      cartItems: [
        {
          id: 1,
          name: '南印度滤压咖啡'
        },
        {
          id: 2,
          name: '冷萃咖啡袋',
        }
      ]
    })
    // highlight-end
})

withSession 方法类似,您可以使用 withFlashMessages 方法在发出 HTTP 请求时设置闪存消息。

ts
const response = await client
  .get('posts/1')
  .withFlashMessages({
    success: '帖子创建成功'
  })

response.assertTextIncludes(`帖子创建成功`)

从响应中读取会话数据

您可以使用 response.session() 方法访问由 AdonisJS 服务器设置的会话数据。该方法将会话数据作为键值对对象返回。

ts
const response = await client
  .post('/posts')
  .json({
    title: '某个标题',
    body: '某个描述',
  })

console.log(response.session()) // 所有会话数据
console.log(response.session('post_id')) // post_id 的值

您可以使用 response.flashMessageresponse.flashMessages 方法从响应中读取闪存消息。

ts
const response = await client.post('/posts')

response.flashMessages()
response.flashMessage('errors')
response.flashMessage('success')

最后,您可以使用以下方法之一为会话数据编写断言。

ts
const response = await client.post('/posts')

/**
 * 断言会话存储中存在特定键(带可选值)
 */
response.assertSession('cart_items')
response.assertSession('cart_items', [{
  id: 1,
}, {
  id: 2,
}])

/**
 * 断言会话存储中不存在特定键
 */
response.assertSessionMissing('cart_items')

/**
 * 断言闪存消息存储中存在闪存消息(带可选值)
 */
response.assertFlashMessage('success')
response.assertFlashMessage('success', '帖子创建成功')

/**
 * 断言闪存消息存储中不存在特定键
 */
response.assertFlashMissing('errors')

/**
 * 断言闪存消息存储中的验证错误
 */
response.assertHasValidationError('title')
response.assertValidationError('title', '请输入帖子标题')
response.assertValidationErrors('title', [
  '请输入帖子标题',
  '帖子标题必须至少 10 个字符长。'
])
response.assertDoesNotHaveValidationError('title')

认证用户

如果您使用 @adonisjs/auth 包在应用程序中认证用户,您可以使用 authApiClient Japa 插件在向应用程序发出 HTTP 请求时认证用户。

第一步是在 tests/bootstrap.ts 文件中注册插件。

ts
// title: tests/bootstrap.ts
// insert-start
import { authApiClient } from '@adonisjs/auth/plugins/api_client'
// insert-end

export const plugins: Config['plugins'] = [
  assert(),
  pluginAdonisJS(app),
  // insert-start
  authApiClient(app)
  // insert-end
]

如果您使用基于会话的认证,请确保也设置会话插件。参见填充会话存储 - 设置

就是这样。现在,您可以使用 loginAs 方法登录用户。该方法接受用户对象作为唯一参数,并将用户标记为当前 HTTP 请求的已登录状态。

ts
import User from '#models/user'

test('获取付款列表', async ({ client }) => {
  const user = await User.create(payload)

  await client
    .get('/me/payments')
    // highlight-start
    .loginAs(user)
    // highlight-end
})

loginAs 方法使用在 config/auth.ts 文件中配置的默认守卫进行认证。但是,您可以使用 withGuard 方法指定自定义守卫。例如:

ts
await client
    .get('/me/payments')
    // highlight-start
    .withGuard('api_tokens')
    .loginAs(user)
    // highlight-end

使用 CSRF 令牌发出请求

如果应用程序中的表单使用 CSRF 保护,您可以使用 withCsrfToken 方法生成 CSRF 令牌并在请求期间将其作为头传递。

在使用 withCsrfToken 方法之前,请在 tests/bootstrap.ts 文件中注册以下 Japa 插件,并确保SESSION_DRIVER 环境变量切换memory

ts
// title: tests/bootstrap.ts
// insert-start
import { shieldApiClient } from '@adonisjs/shield/plugins/api_client'
import { sessionApiClient } from '@adonisjs/session/plugins/api_client'
// insert-end

export const plugins: Config['plugins'] = [
  assert(),
  pluginAdonisJS(app),
  // insert-start
  sessionApiClient(app),
  shieldApiClient()
  // insert-end
]
ts
test('创建帖子', async ({ client }) => {
  await client
    .post('/posts')
    .form(dataGoesHere)
    .withCsrfToken()
})

route 助手

您可以使用 TestContext 中的 route 助手为路由创建 URL。使用 route 助手可确保每当您更新路由时,无需返回并修复测试中的所有 URL。

route 助手接受与全局模板方法 route 接受的相同参数集。

ts
test('获取用户列表', async ({ client, route }) => {
  const response = await client.get(
    // highlight-start
    route('users.list')
    // highlight-end
  )

  response.assertStatus(200)
  response.assertBody({
    data: [
      {
        id: 1,
        email: 'foo@bar.com',
      }
    ]
  })
})