Rohit Kashyap
Rohit Kashyap

Reputation: 1592

How to test for useEffect inside a custom hook?

So I'm pretty new to testing with react. I have a custom hook that I am calling inside a component. I am using the renderHook methods from react-hook-testing-library.

I need to test if the methods inside useEffect inside the custom hook are called. I cant seem to figure this out.

In another case, I need to figure out if trackPdpGtm is not called.

Note: The hook does not return any data. This is mainly for sending analytics information.

Current Approach:

usePdpGtm.js

import { useEffect } from 'react';
import { trackPdpGtm } from 'utils/gtm/pdpGtmUtils';
import _ from 'utils/lodashImports';

export default function useGtmPdp(data = {}) {
  console.log('are you getting called?');
  const { relatedProducts, results, priceInfo, breadcrumbs } = data;
  const relatedProductsLoaded = _.get(relatedProducts, 'relatedProductsLoaded');
  useEffect(() => {
     if (relatedProductsLoaded) {
         trackPdpGtm(data);
     }
  }, [relatedProductsLoaded, results, priceInfo, breadcrumbs]);
}

I need to test if trackPdpGtm are called. Also need to check that its not called in another test case.

gtm.test.js

import { renderHook, cleanup, act } from 'react-hooks-testing-library';
import usePdpGtm from 'utils/hooks/gtmHooks/usePdpGtm';
import useListPageGtm from 'utils/hooks/gtmHooks/useListPageGtm';
import { trackPdpGtm } from 'utils/gtm/pdpGtmUtils';
import { trackListPageGtm } from 'utils/gtm/plpGtmUtils';
import { mount, shallow } from 'enzyme';
import { ProductDescriptionComponent } from 'components/business/ProductDescription/ProductDescriptionComponent';

jest.mock('utils/hooks/gtmHooks/usePdpGtm');
jest.mock('utils/hooks/gtmHooks/useListPageGtm');
jest.mock('utils/gtm/pdpGtmUtils');
jest.mock('utils/gtm/plpGtmUtils');

trackPdpGtm.mockImplementation = jest.fn();
// ALSO TRIED trackPdpGtm.mockImplementation(() => jest.fn());
trackListPageGtm.mockImplementation(() => console.log('adadada'));

describe('analytics helper', () => {
  afterEach(cleanup);

  it('should fire usePdpGtm Hook', async (done) => {
    const pdpData = {
      relatedProducts: {
        collectionProducts: [],
        relatedProductsLoaded: true
      },
      productInfo: {
        variants: [{}],
        breadcrumbs: [{}],
        serviceWarrantyDetails: {
          services: [],
          warranties: [{}]
        }
      },
      priceInfo: [{}],
      breadcrumbs: [{}]
    };
    const { result } = renderHook(() => usePdpGtm(pdpData));
    //THIS IS FAILING
    expect(trackPdpGtm).toHaveBeenCalledTimes(1);
    expect(result.current).toBeUndefined();
  });

   it('should fire usePdpGtm Hook without data', () => {
     const { result } = renderHook(() => usePdpGtm(undefined));
    
     // NEED TO TEST IF trackPdpGtm is NOT called. (also failing)

     expect(trackPdpGtm).toNotBeCalled();
     expect(result.current).toBeUndefined();
   });

});

Also tried using trackGtmPdp.mock.calls.length.toBe(1).

The usePdpGtm hook is called inside ProductDescriptionComponent and receives an object as its argument.

Note: The custom hook is not running during the test. I am not sure but the console.log statements inside are not printed while the test is running.

Any help is highly appreciated.

Upvotes: 6

Views: 12641

Answers (1)

Lin Du
Lin Du

Reputation: 102267

You forget to clear the mock.calls and mock.instances properties of trackPdpGtm function mock. That's why your second test case fails. You can use jest.clearAllMocks() to clear it in afterEach hook.

E.g.

usePdpGtm.ts:

// @ts-nocheck
import { useEffect } from 'react';
import { trackPdpGtm } from './pdpGtmUtils';
import _ from 'lodash';

export default function useGtmPdp(data = {}) {
  console.log('are you getting called?');
  const { relatedProducts, results, priceInfo, breadcrumbs } = data;
  const relatedProductsLoaded = _.get(relatedProducts, 'relatedProductsLoaded');
  useEffect(() => {
    if (relatedProductsLoaded) {
      trackPdpGtm(data);
    }
  }, [relatedProductsLoaded, results, priceInfo, breadcrumbs]);
}

pdpGtmUtils.ts:

export function trackPdpGtm(data) {
  console.log('real track pdp gtm implementation');
}

usePdpGtm.test.ts:

import { renderHook, cleanup } from '@testing-library/react-hooks';
import usePdpGtm from './usePdpGtm';
import { trackPdpGtm } from './pdpGtmUtils';

jest.mock('./pdpGtmUtils');

describe('analytics helper', () => {
  afterEach(() => {
    jest.clearAllMocks();
    cleanup();
  });

  it('should fire usePdpGtm Hook', () => {
    const pdpData = {
      relatedProducts: {
        collectionProducts: [],
        relatedProductsLoaded: true,
      },
      productInfo: {
        variants: [{}],
        breadcrumbs: [{}],
        serviceWarrantyDetails: {
          services: [],
          warranties: [{}],
        },
      },
      priceInfo: [{}],
      breadcrumbs: [{}],
    };
    const { result } = renderHook(() => usePdpGtm(pdpData));
    expect(trackPdpGtm).toHaveBeenCalledTimes(1);
    expect(result.current).toBeUndefined();
  });

  it('should fire usePdpGtm Hook without data', () => {
    const { result } = renderHook(() => usePdpGtm(undefined));
    expect(trackPdpGtm).not.toBeCalled();
    expect(result.current).toBeUndefined();
  });
});

unit test result:

 PASS  examples/65703648/usePdpGtm.test.ts
  analytics helper
    ✓ should fire usePdpGtm Hook (28 ms)
    ✓ should fire usePdpGtm Hook without data (4 ms)

  console.log
    are you getting called?

      at Object.useGtmPdp [as default] (examples/65703648/usePdpGtm.ts:7:11)

  console.log
    are you getting called?

      at Object.useGtmPdp [as default] (examples/65703648/usePdpGtm.ts:7:11)

----------------|---------|----------|---------|---------|-------------------
File            | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------------|---------|----------|---------|---------|-------------------
All files       |   91.67 |      100 |   66.67 |   91.67 |                   
 pdpGtmUtils.ts |      50 |      100 |       0 |      50 | 2                 
 usePdpGtm.ts   |     100 |      100 |     100 |     100 |                   
----------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        5.167 s

Upvotes: 6

Related Questions