Search code examples
typescriptunit-testingjestjsreact-testing-libraryjest-fetch-mock

Testing a function that returns an object with a function - JavaScript (Jest)


I have a situation where I am facing an issue regarding unit testing of a function. There is a function SOP3loginConfig which returns an object, within that object we have a function isSOP3 that returns a boolean, I want to test this function and cover the test part of it.

The actual implementation of the function sop3login.ts

export const SOP3loginConfig = (props: IVariables) => {
  const { i18n } = props;
  return {
    buttonLabel: props.user != null ? i18n('StartJourney') : i18n('logIn'),
    loginLink:"/login?redirectUrl="+window.location.href,
    isSOP3: async() => {
      let userData = await ADCServices.getUserInfo();
      if (!userData.session.tammUserInfo || userData.session.tammUserInfo.Type!="SOP3") {
        props.SOP3toggleModal(props,true);
        setTimeout(()=>props.SOP3toggleModal(props,false),5000)
        return false;
      } else {
        return true;
      }
    },
  };
};

My Integration work.ts

import { SOP3loginConfig } from 'client/.../sop3login';

const start = async (props: IVariables) => {
  if (props.user) {
    if (await SOP3loginConfig(props).isSOP3()) {
      props.history.push('/adc/card-renewal/customs');
    }
  } else {
    props.history.push(SOP3loginConfig(props).loginLink);
  }
};

Unit testing implmentation of mine work.test.ts

  describe('Start SOP3loginConfig should be called', () => {
    it('Should call the SOP3', async () => {
      props.user = true;
      // const isSOP3Mock = () => {
      //   return true;
      // };
      // let SOP3loginConfig = async (props: any) => {
      //   return true;
      //   // return {
      //   //   isSOP3: isSOP3Mock,
      //   // };
      // };
      let SOP3loginConfig = jest.fn(props => {
        return {
          isSOP3: jest.fn(() => {
            return true;
          }),
        };
      });
      functions.start(props);
      expect(await SOP3loginConfig(props).isSOP3).toHaveBeenCalled();
      expect(props.history.push).toHaveBeenCalled();
    });
  });

Error I am getting

    expect(jest.fn()).toHaveBeenCalled()

    Expected number of calls: >= 1
    Received number of calls:    0

       97 |       });
       98 |       functions.start(props);
    >  99 |       expect(await SOP3loginConfig(props).isSOP3).toHaveBeenCalled();
          |                                                   ^
      100 |       expect(props.history.push).toHaveBeenCalled();
      101 |     });
      102 |   });

All I want to is cover the if (await SOP3loginConfig(props).isSOP3()) part in work.ts.


Solution

  • Here is the unit test solution:

    sop3login.ts:

    import ADCServices from './adc.service';
    
    export interface IVariables {
      user: any;
      history: IHistory;
      i18n(name: string): any;
      SOP3toggleModal(props: IVariables, flag: boolean): void;
    }
    
    interface IHistory {
      push(router: string): any;
    }
    
    export const SOP3loginConfig = (props: IVariables) => {
      const { i18n } = props;
      return {
        buttonLabel: props.user != null ? i18n('StartJourney') : i18n('logIn'),
        loginLink: '/login?redirectUrl=' + window.location.href,
        isSOP3: async () => {
          const userData = await ADCServices.getUserInfo();
          if (!userData.session.tammUserInfo || userData.session.tammUserInfo.Type !== 'SOP3') {
            props.SOP3toggleModal(props, true);
            setTimeout(() => props.SOP3toggleModal(props, false), 5000);
            return false;
          } else {
            return true;
          }
        },
      };
    };
    

    adc.service.ts:

    export default class ADCServices {
      public static async getUserInfo() {
        return {
          session: {
            tammUserInfo: {
              Type: 'real type',
            },
          },
        };
      }
    }
    

    work.ts:

    import { SOP3loginConfig, IVariables } from './sop3login';
    
    const start = async (props: IVariables) => {
      if (props.user) {
        if (await SOP3loginConfig(props).isSOP3()) {
          props.history.push('/adc/card-renewal/customs');
        }
      } else {
        props.history.push(SOP3loginConfig(props).loginLink);
      }
    };
    
    export { start };
    

    work.test.ts:

    import { start } from './work';
    import { SOP3loginConfig, IVariables } from './sop3login';
    
    jest.mock('./sop3login.ts', () => {
      const mObj = {
        isSOP3: jest.fn(),
      };
      return { SOP3loginConfig: jest.fn(() => mObj) };
    });
    
    describe('Start SOP3loginConfig should be called', () => {
      it('Should call the SOP3', async () => {
        const mProps: IVariables = {
          user: true,
          history: { push: jest.fn() },
          i18n: jest.fn(),
          SOP3toggleModal: jest.fn(),
        };
        await start(mProps);
        expect(SOP3loginConfig).toBeCalledWith(mProps);
        expect(SOP3loginConfig(mProps).isSOP3).toBeCalledTimes(1);
      });
    });
    

    Unit test results with coverage report:

     PASS  src/stackoverflow/59627009/work.test.ts
      Start SOP3loginConfig should be called
        ✓ Should call the SOP3 (7ms)
    
    ----------|----------|----------|----------|----------|-------------------|
    File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ----------|----------|----------|----------|----------|-------------------|
    All files |    77.78 |       50 |      100 |    71.43 |                   |
     work.ts  |    77.78 |       50 |      100 |    71.43 |               6,9 |
    ----------|----------|----------|----------|----------|-------------------|
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        4.078s, estimated 10s
    

    Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/59627009