这是一个实现细节吗?或者它是一个应该用 Jest 测试的功能吗?

Is this an implementation detail? Or is it a feature that should be tested with Jest?

我有一个更新时间的问候组件。根据一天中的时间打印问候语

我有一个名为 getTimeOfDay 的函数并测试该函数是否正常工作。如果你有兴趣 view my tests here.

在我的组件中,我有一个计时器,它每分钟检查一次以查看时间消息是否应该更新。

const [date, setDate] = useState(new Date())
const timeOfDay = getTimeOfDay(date)

useEffect(() => {
  const timer = setInterval(() => {
    setDate(new Date())
  }, 60000)

  return () => {
    clearInterval(timer)
  }
}, [])

我一直在反复考虑是否要测试此消息是否随着时间的推移而正确更改。我知道测试实现细节是不好的做法,并且不确定这是应该测试的实现细节还是功能。

如果这是我应该测试的东西,我似乎无法轻松地开玩笑地实现一个计时器来检查消息,然后将计时器加速 8 小时。你有什么想法?

您可以更改日期以用于测试目的,使用模拟函数覆盖测试中的日期。

    Date.now = jest.fn(() => new Date( "2021-01-01 8:50:00"))
    
    const result = getTimeOfDay(switchToMorning)
    expect(result).toEqual('Morning')

我最后的测试如下:

describe('Greeting', () => {
  const name = 'Jack'
  
  it('renders component as expected', () => {
    const wrapper = mount(<Greeting name={name} /> )
    expect(wrapper.text().includes(name)).toBe(true)
  })

  it('Should update message after time', () => {
    jest.useFakeTimers()
    setMockDate(new Date('Feb 22, 2021 11:59:00'))
    const wrapper = mount(<Greeting name={name} />)

    const greetingText = wrapper.text()
    setMockDate(new Date('Feb 22, 2021 12:00:00'))
    jest.advanceTimersByTime(60000)
    expect(wrapper.text()).not.toBe(greetingText)
  })

  it('Should clear interval on unmount', () => {
    const spyOnSetInterval = jest.spyOn(window, 'setInterval')
    const spyOnClearInterval = jest.spyOn(window, 'clearInterval')
    spyOnSetInterval.mockReturnValueOnce((33 as unknown) as NodeJS.Timeout)
    const wrapper = mount(<Greeting name={name} />)

    wrapper.unmount()
    expect(spyOnClearInterval).toHaveBeenCalledWith(33)
  })
})

我创建了一个辅助函数来模拟日期。最终版本为:

/**
 * @param {Date} expected
 * @returns {Function} Call to remove Date mocking
 */
const setMockDate = (expected: Date): AnyObject => {
  const RealDate = Date

  function MockDate(mockOverride?: Date | number) {
    return new RealDate(mockOverride || expected)
  }

  MockDate.now = () => expected.getTime()
  MockDate.prototype = RealDate.prototype

  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  global.Date = MockDate as any

  return () => {
    global.Date = RealDate
  }
}

export default setMockDate