0

I've been struggling for a little while now with accessing a variable that is inside the constructor of a component, to test different methods in which it's being used. See example below.

The constructor of my component Timer. I want to access countDown within my timer.test.js file.

constructor(props){
        super(props)
        countDown = null,
        ...
}

Below is one of the function I want to test (in which coutDown is used) :

    pauseTimer(){
        this.setState({timerIsActive : false})
        clearInterval(countDown)
    }

Below is an example of my timer.test.js file :

import React from 'react'
import renderer from 'react-test-renderer'
import {shallow} from 'enzyme'



const wrapper = shallow(<Timer/>)
const componentInstance = wrapper.instance()



describe('My timer ', () => {
    it('Shallow rendering', () => {
        expect(wrapper).toMatchSnapshot()
    })

    it('Should update state customeTimeValue to 20', ()=>{
        componentInstance.setState({...componentInstance.state, customTimeValue : {focus : '20'}})
        expect(componentInstance.state.customTimeValue.focus).toEqual('20')
    })


    it('isValueOutOfRange with 20 should return 20', ()=>{
        componentInstance.setState({...componentInstance.state, customTimeValue : {focus : '20'}})
        expect(componentInstance.isValueOutOfRange('focus')).toEqual(20)
    })
    it('isValueOutOfRange with 67 should return 59', ()=>{
        componentInstance.setState({...componentInstance.state, customTimeValue : {focus : '67'}})
        expect(componentInstance.isValueOutOfRange('focus')).toEqual(59)

I have already searched on multiple posts and docs but none of these are what I am looking for. I've tried different things on my own but with no results.

I hope you guys will be able to help me. Thanks !

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Damien.B
  • 1
  • 1
  • 3
  • it's impossible. and you don't actually need doing that. but to explain how to test your component I need to realize how this variable is used. by your code sample it looks like global variable shared across all `` instances. Is it so? – skyboyer Jul 14 '19 at 15:07
  • I am using it in a function called **decrementTimer** : countDown = setInterval(()=>{... and when I pause the timer I want to make sure that **countDown** does not contain the setInterval ( which is supposed to be cleared in the function mentioned above). – Damien.B Jul 14 '19 at 16:45
  • is it globa really global variable? – skyboyer Jul 14 '19 at 17:02
  • I don't really understand but it's declared in the constructor and used in 2 methods of the component. Btw I use only one instance of `` – Damien.B Jul 14 '19 at 18:04
  • it's strange but ok. better provide full source since I'm interested in all places where it;s used and also how does it affect `render()` method – skyboyer Jul 14 '19 at 18:21
  • Sure, here is the link to my github project : https://github.com/DamienBrn/Pomodoro-Timer – Damien.B Jul 14 '19 at 18:31

2 Answers2

0

According to your code <Timer> is using global countDown and hard-coded configuration for job/recreation intervals.

So I expect you use jest.useFakeTimers() in order to test this component. Something like

const wrapper = shallow(<Timer />);
expect(getTime(wrapper)).toEqual('00:00');
expect(modeIsWork(wrapper)).toBeTruthy();
jest.advanceTimersByTime(jobInterval - 1);
expect(getTime(wrapper)).toEqual('04:59');
// should still be in "Work" mode
expect(modeIsWork(wrapper)).toBeTruthy();
jest.advanceTimersByTime(2);
// should get into "Rest" mode
expect(modeIsWork(wrapper)).toBeFalsy(); 
expect(getTime(wrapper)).toEqual('00:00');

where getTime and modeIsWork are hypothetical helper functions that check/return some with wrapper.find() and other Enzyme's methods. No access to state but checking element's classes, attributes and inner text.

Returning back to countDown that current is global(not sure if it's intentionally). If it's global intentionally we need to ensure that

const timer1 = shallow(<Timer />);
const timer2 = shallow(<Timer />);
clickPause(timer1);
jest.advanceTimersByTime(1001);
expect(getTime(timer2)).toEqual('00:00'); // second timer is also paused

But I suspect it's made global occasionally and actually you need test opposite: that pausing one timer does not stop another one.

PS among shared timer I also see decrementTimer relied on setInterval but not checking actual time to be fragile. While timer drift seems to be fixed in modern browsers it's still not guaranteed timer will be called exactly after 1000ms. And since you're decrementing inner counter each time unconditionally it means you definitely will get it being late more and more. To make solution more reliable you'd need also to check current datetime and to mock that you'd probably need something like lolex to mock setInterval and global.Date consistently with less manual work.

skyboyer
  • 22,209
  • 7
  • 57
  • 64
0

Thanks for the detailed answer. I managed to find something to test if my setInterval was working (see below) but now it seems that I can't reset the fake timers. I've searched through docs and posts but the solutions provided do not work for me.

it('decrementTimer in "focus state" after 1 second should return 4:59', ()=> 
{
        jest.useFakeTimers();
        componentInstance.setState({...componentInstance.state, timerState : 
         'focus'})
        expect(componentInstance.state.timer.focus.minutes).toEqual(5);
        expect(componentInstance.state.timer.focus.seconds).toEqual(0);
        componentInstance.decrementTimer();
        jest.advanceTimersByTime(1000);
        expect(componentInstance.state.timer.focus.minutes).toEqual(4);
        expect(componentInstance.state.timer.focus.seconds).toEqual(59);
        jest.useRealTimers();
    })
it('decrementTimer in "focus state" after 1 second should return 4:59', ()=> 
{
        jest.useFakeTimers();
        componentInstance.setState({...componentInstance.state, timer : {
            focus : {
                minutes : 5,
                seconds : 0
            }
        },
        timerState  :'focus'})
        expect(componentInstance.state.timer.focus.minutes).toEqual(5);
        expect(componentInstance.state.timer.focus.seconds).toEqual(0);
        componentInstance.decrementTimer();
        jest.advanceTimersByTime(1000);
        expect(componentInstance.state.timer.focus.minutes).toEqual(4);
        expect(componentInstance.state.timer.focus.seconds).toEqual(59);
        jest.useRealTimers();
    })

So here we have the same test twice (for test purposes). The first test works, but the second one does not because after the setInterval gets called, it actually decrements by 2secs (and not by one as mentioned in advanceTimersByTime) See below :

expect(received).toEqual(expected) // deep equality

Expected: 59
Received: 58

  158 |             jest.advanceTimersByTime(1000);
  159 |             expect(componentInstance.state.timer.focus.minutes).toEqual(4);
> 160 |             expect(componentInstance.state.timer.focus.seconds).toEqual(59);
      |                                                                 ^
  161 |             jest.useRealTimers();
  162 |         })
  163 | 

  at Object.toEqual (timer.test.js:160:65)

So I guess there are two timers running at the same time. Making this work would solve my initial problem. Thanks !

Damien.B
  • 1
  • 1
  • 3