结构化处理程序

构建请求处理程序的最佳实践。

处理程序结构

¥Handlers structure

我们建议使用单个 handlers.js 模块来描述网络的成功状态(快乐路径)。首先采用成功行为,除了你可能添加的任何运行时覆盖之外,你始终有一个基本的请求处理。

¥We recommend utilizing a single handlers.js module to describe the successful states (happy paths) of your network. Going success behavior-first, you always have a base request handling in addition to any runtime overrides you may add.

// mocks/handlers.js
import { http, HttpResponse } from 'msw'
 
export const handlers = [
  http.get('/user', () => {
    return HttpResponse.json({ name: 'John Maverick' })
  }),
]
import { http } from 'msw'
import { server } from '../mocks/node'
 
afterEach(() => {
  server.resetHandlers()
})
 
// This test takes advantage of the happy path behavior
// listed in the "handlers.js", which means that requests
// to the "GET /user" endpoint always return a mocked response.
it('displays the user info', () => {
  render(<UserComponent />)
  expect(screen.getByText('John Maverick')).toBeVisible()
})
 
it('handles errors when fetching the user', () => {
  // This test, however, needs a reliable way to reproduce
  // a server error to test the UI. Instead of adding this
  // behavior in the "handlers.js", add a runtime override
  // so that requests to "GET /user" always return an error.
  server.use(
    http.get('/user', () => {
      return new HttpResponse(null, { status: 500 })
    })
  )
 
  render(<UserComponent />)
  expect(screen.getByRole('alert')).toHaveText('Error!')
})

了解有关 .use()网络行为覆盖 的更多信息。

¥Learn more about .use() and network behavior overrides.

利用网络行为覆盖在需要时将同一资源的行为在其快乐状态(在 handlers.js 中)和按需状态(如错误响应)之间拆分。

¥Utilize network behavior overrides to split the behavior of the same resource between its happy state (in handlers.js) and its on-demand states (like error responses) whenever you need them.

处理大型处理程序

¥Dealing with large handlers

复杂的系统可能有复杂的 API。模拟它们时,这种复杂性可能会导致同时存在大量请求处理程序。以下是处理大型网络描述的几种方法。

¥Complex systems may have complex APIs. When mocking them, that complexity may result in a large number of request handlers present at the same time. Here’s a couple of ways how you can to handle large network descriptions.

组请求处理程序

¥Group request handlers

与同一资源的所有服务器端行为不必存在于单个请求处理程序中类似,产品不同区域的所有网络描述不必写在单个 handlers 数组中。

¥Similar to how all server-side behaviors for the same resource don’t have to live in a single request handler, all network descriptions for different areas of your product don’t have to be written in a single handlers array.

相反,考虑在文件系统级别拆分它们,按域对它们进行分组,然后稍后编写处理程序列表。

¥Instead, consider splitting them on the file system level, grouping them by domain, and composing the list of handlers later.

mocks/
  handlers/
    user.js
    checkout.js
    index.js
// mocks/handlers/user.js
import { http } from 'msw'
 
// These request handlers focus on the endpoints
// that concern the user.
export const handlers = [
  http.get('/user', getUserResolver),
  http.post('/login', loginResolver),
  http.delete('/user/:userId', deleteUserResolver),
]
// mocks/handlers/index.js
import { handlers as userHandlers } from './user'
import { handlers as checkoutHandlers } from './checkout'
 
// The root-level request handlers combine
// all the domain-based handlers into a single
// network description array.
export const handlers = [...userHandlers, ...checkoutHandlers]

对请求处理程序进行分组在大型测试套件中也很有用,因为你可以在特定测试期间仅应用处理程序的子集。

¥Grouping the request handlers can also be beneficial in large test suites as you can apply only a subset of handlers during a particular test.

// test/user/login.test.js
import { server } from '../../mocks/node'
import { handlers as userHandlers } from '../../mocks/handlers/user'
 
server.use(...userHandlers)

要充分利用基于域的请求处理程序,你可以考虑在没有任何基本请求处理程序的情况下调用 setupServer()

¥To take the full advantage of domain-based request handlers, you may consider calling setupServer() without any base request handlers.

抽象重复逻辑

¥Abstract repeated logic

作为第一步,我们建议将重复逻辑抽象为实用函数,然后你可以在不同的请求处理程序中重复使用这些函数。

¥As the first step, we recommend abstracting the repetitive logic into utility functions that you can then reuse across different request handlers.

import { http } from 'msw'
import { utilOne, utilTwo } from './utils'
 
export const handlers = [
  http.get('/user', async ({ request }) => {
    const result = await utilOne(request)
  }),
  http.post('/login', () => {
    utilTwo()
  }),
]

对于更复杂的场景,你可以引入 高阶响应解析器,一次封装多个请求处理逻辑部分。

¥For more complex scenarios, you can introduce higher-order response resolvers that encapsulate multiple parts of request handling logic at once.

import { http, HttpResponse } from 'msw'
import { withAuth } from './withAuth'
 
export const handlers = [
  http.get('/cart', withAuth(getCartResolver)),
  http.post('/checkout/:cartId', withAuth(addToCartResolver)),
]