This question puzzled my mind for some time and I noticed that there is no good post which provides an up-to-date answer to the challenge. Basically, this article will help you solve your testing problem in React and I also provide you a sample project with a working application.

Description of the problem

You have a component which adds some event listener inside useEffect() block. You wrote some unit test, but quickly realised that your unit test runs the code inside useEffect, but it does not run the function that your event listener is adding. So, you should basically mock addEventListener to solve it, but how?

Sample Project: offline-modal

offline-modal is a simple application which utilizes online/offline events. (See reference here: developer.mozilla.org)

When you turn off internet on your machine (or by using the throttle functionality on Chrome), the application shows some information to the user. After the connection is asynchronously restored, the user receives the feedback about connected internet for 3 seconds. And this message also finally disappears.

Offline Modal Demo

Implementation of offline-modal > OfflineModal.tsx

Before we jump into the unit testing discussion, it is wise to review the implementation.

const OfflineModal: React.FC = () => {
    const [connStatus, setConnStatus] = useState<number>(ConnectionStatus.Online);
    const connStatusRef = useRef(connStatus);
    connStatusRef.current = connStatus;
    useEffect(() => {
        const toggleOffline = () => {
            setConnStatus(ConnectionStatus.Offline);
        };
        let timer: NodeJS.Timeout | undefined;
        const toggleOnline = () => {
            setConnStatus(ConnectionStatus.SwitchingOnline);
            timer = setTimeout(() => {
                if (connStatusRef.current !== ConnectionStatus.Offline) {
                    setConnStatus(ConnectionStatus.Online);
                }
            }, 3000);
        };

        window.addEventListener('online', toggleOnline);
        window.addEventListener('offline', toggleOffline);
        return () => {
            window.removeEventListener('online', toggleOnline);
            window.removeEventListener('offline', toggleOffline);
            if (timer) {
                clearTimeout(timer);
            }
        };
    }, []);

I will explain the critical lines here.

Line 2: This our state management which keeps track of current connectivity status.

Line 19 and 20: I basically add event listeners to follow connection status here. Two separate functions are assigned to online and offline cases.

Line 22 and 23: Before the component (OfflineModal) is unmounted, we remove the event listeners.

In the latter part of the component, I render 3 different connection status (Offline, SwitchingOnline, Online) in a Switch statement to inform the user. That’s basically it.

Unit Test Implementation > OfflineModal.test.tsx

This is the critical part. I wrote the function below to mock internet connection in Jest environment.

const mockInternetConnection = (status: string) => {
    const events = {};
    jest.spyOn(window, 'addEventListener').mockImplementation((event, handle, options?) => {
        // @ts-ignore
        events[event] = handle;
    });
    const goOffline = new window.Event(status);
    act(() => {
        window.dispatchEvent(goOffline);
    });
};

Line 1: I created a parameter called status. In this example, example input could be ‘online’ or ‘offline’ string values. These are actually the event names to be added.

Line 2: An empty json object is created.

Line 3: The core of mocking happens in this scope. We spy on window.addEventListener call. Whenever subject code runs this function in due implementation, we will add the corresponding event type into its events array. So that, we will be able to run requested event in our unit test. This event can be any other event such as click, keydown, etc.

Line 7: This line creates a fake event. But it does nothing on its own. I only assigns the fake event object into a const called goOffline.

Line 9: Here we trigger our fake event just like the implementation code does in reality.

After creating the function (mockInternetConnection), writing the unit test suite is much simpler.

describe('OfflineModalComponent', () => {
    it('switches offline and back online', () => {
        render(<OfflineModal />);
        mockInternetConnection('offline');
        expect(screen.getByText('You are offline')).toBeInTheDocument();
        mockInternetConnection('online');
        expect(screen.getByText('It worked! You are back online! :)')).toBeInTheDocument();
    });
});

This unit test turns off the user’s internet connection and verifies the correct output. Later on, it switches the connection back online and runs one more check.

I hope this blog post helps you solve a similar problem on your side 🙂 Good luck!

Here is the link to the GitHub repository: https://github.com/SercanSercan/storksnest/tree/master/offline-modal

Sercan Leylek

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s