【问题标题】:Spy on mock service worker (msw)?监视模拟服务人员(msw)?
【发布时间】:2020-12-04 02:32:10
【问题描述】:

在观看了this example 如何使用它来测试 React 应用程序中的 API 调用之后,我开始使用 msw (mock service worker)

我们有什么办法可以监视模拟服务工作者?

例如:

import React from 'react'
import { render, act, await } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { rest } from 'msw'
import { setupServer } from 'msw/node'

import SearchBox from '.'

const fakeServer = setupServer(
  rest.get(
    'https://api.flickr.com/services/rest/?method=flickr.photos.search',
    (req, res, ctx) => res(ctx.status(200), ctx.json({ data: { photos: { photo: [] },},}))
  )
)

beforeAll(() => {fakeServer.listen()})
afterEach(() => {fakeServer.resetHandlers()})
afterAll(() => fakeServer.close())

test('it calls Flickr REST request when submitting search term', async () => {
  const { getByLabelText } = render(<SearchBox />)
  const input = getByLabelText('Search Flickr')
  const submitButton = getByLabelText('Submit search')

  await act(async () => {
    await userEvent.type(input,'Finding Wally')
    await userEvent.click(submitButton)
  })

  await wait()

  // TODO: assert that the fakeServer was called once and with the correct URL
})

要测试的组件如下所示:

import React, { useState } from 'react'
import axios from 'axios'

import './index.css'

function SearchBox({ setPhotos }) {
  const [searchTerm, setSearchTerm] = useState('')

  const handleTyping = (event) => {
    event.preventDefault()
    setSearchTerm(event.currentTarget.value)
  }

  const handleSubmit = async (event) => {
    event.preventDefault()
    try {
      const restURL = `https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=${
        process.env.REACT_APP_API_KEY
      }&per_page=10&format=json&nojsoncallback=1'&text=${encodeURIComponent(
        searchTerm
      )}`
      const { data } = await axios.get(restURL)
      const fetchedPhotos = data.photos.photo
      setPhotos(fetchedPhotos)
    } catch (error) {
      console.error(error)
    }
  }

  return (
    <section style={styles.container}>
      <form action="" method="" style={styles.form}>
        <input
          aria-label="Search Flickr"
          style={styles.input}
          value={searchTerm}
          onChange={handleTyping}
        />
        <button
          aria-label="Submit search"
          style={styles.button}
          onClick={handleSubmit}
        >
          SEARCH
        </button>
      </form>
    </section>
  )
}

我有一个工作测试,但我觉得它倾向于实施测试,因为它使用了setPhotos 上的间谍

test('it calls Flickr REST request when submitting search term', async () => {
  const fakeSetPhotos = jest.fn(() => {})
  const { getByLabelText } = render(<SearchBox setPhotos={fakeSetPhotos} />)
  const input = getByLabelText('Search Flickr')
  const submitButton = getByLabelText('Submit search')

  await act(async () => {
    await userEvent.type(input, 'Finding Walley')
    await userEvent.click(submitButton)
  })

  await wait()

  expect(fakeSetPhotos).toHaveBeenCalledWith([1, 2, 3])
})

【问题讨论】:

  • 为什么你认为 setPhotos 是一个测试替身,表示实现测试?回调 prop 是组件期望的行为的一部分。
  • 我正在传递一个函数 setPhotos 来更新父状态,但可能是我使用了反应上下文而不是它不会作为道具出现。
  • 但这将是行为的改变,在这个组件的级别。如果您希望能够在不更改测试的情况下更改父子通信方式,则不应在它们之间的接口处进行测试。请不要在标题中添加标签,这就是标签的用途。
  • 但无论如何,这些都与您实际要求的内容没有直接关系,因为这个问题实际上与 setPhotos 行为无关,更多的是关于 other 部分组件的行为,想知道它发出了它应该发出的请求。部分问题在于关注点分离 - 如果您将实际的请求提取到协作者,则可以将其替换为简单的测试替身,组件只需为其提供搜索词 (expect(service.getPhotos).toHaveBeenCalledWith(searchInput))。跨度>
  • 这是一个很好的观点,父母和孩子之间的接口测试很有价值。我还想测试它是否调用了正确的 REST URL。

标签: javascript reactjs unit-testing jestjs


【解决方案1】:

mswjs 的开发人员非常友善且乐于助人。他们花时间to advice我了解如何处理它。

TLDR;

我得到的当前工作测试很好 - 只是建议了 jest.fn() 的替代方案 - 我确实喜欢他们建议的可读性:

test('...', async () => {
  let photos

  // Create an actual callback function
  function setPhotos(data) {
    // which does an action of propagating given data
    // to the `photos` variable.
    photos = data
  }

  // Pass that callback function as a value to the `setPhotos` prop
  const { getByLabelText } = render(<SearchBox setPhotos={setPhotos} />)

  // Perform actions:
  // click buttons, submit forms

  // Assert result
  expect(photos).toEqual([1, 2, 3])
})

我想测试的另一件事是它实际上调用了一个有效的 REST URL。

您可以在响应解析器中反映无效的查询参数。 如果查询参数丢失/无效,您的真实服务器将不会 产生预期的数据,对吧?因此,使用 MSW,您的“真正的服务器”是 您的响应解析器。检查该查询的存在或值 参数并在该参数无效的情况下引发错误。

rest.get('https://api.flickr.com/services/rest/?method=flickr.photos.search', 
     (req, res, ctx) => {   const method = req.url.searchParams.get('method')

  if (!method) {
    // Consider a missing `method` query parameter as a bad request.
    return res(ctx.status(400))   }

  // Depending on your logic, you can also check if the value of the `method`   // parameter equals to "flickr.photos.search".

  return res(ctx.json({ successful: 'response' })) })

现在,如果您的应用错过了请求 URL 中的方法查询参数,它会得到 400 响应,并且不应在此类不成功响应的情况下调用 setPhotos 回调。

【讨论】:

    【解决方案2】:

    如果您想避免嘲笑,您可以监视axios.get 并断言它被正确调用。

    test('it calls Flickr REST request when submitting search term', async () => {
      const getSpy = jest.spyOn(axios, 'get');
      const { getByLabelText } = render(<SearchBox />)
      const input = getByLabelText('Search Flickr')
      const submitButton = getByLabelText('Submit search')
    
      await act(async () => {
        await userEvent.type(input,'Finding Wally')
        await userEvent.click(submitButton)
      })
    
      await wait()
    
      expect(getSpy).toHaveBeenCalledTimes(1)
    })
    

    【讨论】:

    • 这是一个很好的建议(我赞成)——我唯一的缺点是它是一个实现测试而不是 BDD 测试
    猜你喜欢
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 1970-01-01
    • 2021-03-18
    • 2019-04-09
    • 1970-01-01
    • 2019-11-06
    • 2021-08-26
    相关资源
    最近更新 更多