reactjsjestjsbest-in-place

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


I have a greeting component that updates the time of day. Depending on the time of day the greeting will print

I have function called getTimeOfDay and test that this function is working. If you're interested view my tests here.

In my component I have a timer that checks every minute to see if the time of day message should update.

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

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

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

I have been going back and forth on whether to test that this message changes correctly as the time passes. I know that testing implementation details is bad practice and was not sure if this was an implementation detail or a feature that should be tested.

If this is something I should test, I can't seem to easily implement a timer in jest that checks the message and then speeds up the timer 8 hours. What are your thoughts?


Solution

  • My final tests are as followed:

    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)
      })
    })
    

    I created a helper function to mock the date. The final version is:

    /**
     * @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