ashik ahmed
ashik ahmed

Reputation: 37

Jest to test a class method which has inner function

I'm writing unit test for once of my .ts file. Where I'm facing a problem and unable to find the solution. Hopefully someone can help me to resolve it.

Problem

While writing unit test. I'm unable to test the value for profile. After calling a method called getProfile().

File setup

Profile.ts

import { getProfileAPI} from "./api";

class ProfileDetails implements IProfileDetails  {

    public profile: string = ''

    constructor() {}

    getProfile = async () => {
        const { data } = await getProfileAPI();
        if (data) {
            this.profile = data
        }
    };
 }

const profileDetail = new ProfileDetails();
export default profileDetail;

Profile.spec.ts

import Profile from './Profile';

describe('Profile', () => {
    it('getProfile', async () => {
        Profile.getProfile = jest.fn();
        await Profile.getProfile();
        expect(Profile.getProfile).toHaveBeenCalled();
    });
});

So the challenge I'm facing here is, I can able to mock the getProfile method. But I'm not able to mock the getProfileAPI function which is called inside the getProfile method.

How can I mock a function which is called inside a mocked method (or) is there any other way to resolve this. Kindly help.

Thanks in advance.

Upvotes: 0

Views: 1872

Answers (1)

Robin Michay
Robin Michay

Reputation: 817

Before answering your questions, I may have some comments :

    1. your test is wrong, all it does is calling the method then checking if it is called, of course it will always pass !
    1. you are not really mocking, in fact you're erasing the old method and it may have some impacts on other tests.
    1. your method "getProfile" should be called "getAndSetProfile", or "syncProfile", or something like that, getProfile is confusing for a developer, he will think it only get the profile and returns it.
    1. I don't recommend creating & exporting an instance of ProfileDetails like this, you should take a look on DI (Dependency Injection) with typedi for example.

Do not forget :

A unit test means that any dependency inside your "unit" should be mock, you must only test the logic inside your "unit" (in your case, the getProfile function, or the class itself).

Here, you are invoking a method called "getProfileAPI" from another service that is not mocked, so you are currently testing its logic too.

This test should work :

Profile.spec.ts

jest.mock('./api', () => ({
  getProfileAPI: jest.fn(),
}));
import { getProfileAPI } from "./api";
import Profile from './Profile';

describe('Profile', () => {
    it('getProfile', async () => {
        await Profile.getProfile();
        expect(getProfileAPI).toHaveBeenCalled();
    });
});

In our example, Profile.profile will be empty, because even if we mocked to getProfileAPI method, we didn't make it return something. You could test both cases :

jest.mock('./api', () => ({
  getProfileAPI: jest.fn(),
}));
import { getProfileAPI } from "./api";
import Profile from './Profile';

const mockGetProfileAPI = getProfileAPI as jest.Mock; // Typescript fix for mocks, else mockResolvedValue method will show an error

describe('Profile', () => {
    describe('getProfile', () => {
        describe('with data', () => {
            const profile = 'TEST_PROFILE';

            beforeEach(() => {
                mockGetProfileAPI.mockResolvedValue({
                    data: profile,
                });
            });

            it('should call getProfileAPI method', async () => {
                await Profile.getProfile();
                expect(mockGetProfileAPI).toHaveBeenCalled(); // Please note that "expect(getProfileAPI).toHaveBeenCalled();" would work
            });

            it('should set profile', async () => {
                await Profile.getProfile();
                expect(Profile.profile).toBe(profile);
            });
        });

        describe.skip('with no data', () => {
            it('should not set profile', async () => {
                await Profile.getProfile();
                expect(Profile.profile).toStrictEqual(''); // the default value
            });
        });
    });
});

NB : I skipped the last test because it won't work in your case. Profile isn't recreated between tests, and as it is an object, it keeps the value of Profile.profile (btw, this is a bit weird) between each tests. This is one of the reasons why you should not export a new instance of the class.

Upvotes: 1

Related Questions