Search code examples
typescriptjestjsmockingts-jesttesting-library

The test results are not constant. in jest, testing-library


The results keep changing when I do the yarn test.

this is error name "Request failed with status code 400"

but i already Api's request function is already mocking

like this

import * as api from '@api/user';

jest.mock('@api/user');

(api.fetchApiKeyUpdate as jest.Mock).mockImplementationOnce(() => {
      throw { response: { data: { code: 'EX_INVALID_APIKEY_2015' } } };
});


(api.fetchApiKeyUpdate as jest.Mock).mockImplementationOnce(() => ({
      status: 'success',
      user: {
        id: '99b1231230',
        country: 'KR',
        language: 'ko',
      },
    }));

The test made of this is passing. But tests often fail. I wonder what I should suspect.

export const fetchApiKeyUpdate = async ({
  exchange,
  apiKey,
  secretKey,
  passphrase,
  otpCode,
}: ApiKeyUpdateRequest): Promise<UserInfoUpdateResponse | ApiAuthResponse> => {
  const { data } = await axios.post(
    apiKeyUpdateUrl,
    { apiKey, secretKey, passphrase, otpCode },
    { headers: { exchange } },
  );
  return data;
};

The bottom is part of the code that I modified.

jest.mock('@api/user');

describe('API Register Success', () => {
  const mockResponse = {
    status: 'success',
    user: {
      id: '99bd10e123400',
      userName: 't123st07',
      receiveMarketingInfo: true,
    },
  };

  beforeEach(() => {
    (api.fetchApiKeyUpdate as jest.Mock).mockResolvedValueOnce(mockResponse);
  });

  it('키인증 성공시 아이콘 변경', async () => {
    const { container } = render(
      <ApiRegistrationBinanceTab
        isOpen
        handleOpenTab={jest.fn()}
      />,
    );

    userEvent.type(screen.getByPlaceholderText(/api key/i), 'apikey');
    userEvent.click(screen.getByRole('button', { name: /Verify/i }));

    await waitFor(() => {
      expect(container.querySelector('#certified-icon')).toBeTruthy();
    });
  });
});

describe('API Register Fail', () => {
  const mockResponse = { response: { data: { code: 'EX_INVALID_APIKEY_2015' } } };

  beforeEach(() => {
    (api.fetchApiKeyUpdate as jest.Mock).mockRejectedValueOnce(mockResponse);
  });

  it('remove input value if error code EX_INVALID_APIKEY_2015 or API_MANAGEMENT_ALREADY_REGISTERED', async () => {
    render(
      <ApiRegistrationBinanceTab
        isOpen
        handleOpenTab={jest.fn()}
      />,
    );

    userEvent.type(screen.getByPlaceholderText(/api key/i), 'apikey');
    userEvent.click(screen.getByRole('button', { name: /Verify/i }));

    await waitFor(() => {
      expect(screen.getByPlaceholderText(/api key/i)).toHaveValue('');
    });
  });
});
FAIL src/components/articles/modal/custom/forgotPassword/ForgotPassword.spec.tsx
  ● 비밀번호 변경 스텝별로 진행

    Request failed with status code 400

      at createError (node_modules/axios/lib/core/createError.js:16:15)
      at settle (node_modules/axios/lib/core/settle.js:17:12)
      at XMLHttpRequest.handleLoad (node_modules/axios/lib/adapters/xhr.js:62:7)
      at XMLHttpRequest.<anonymous> (node_modules/jsdom/lib/jsdom/living/helpers/create-event-accessor.js:32:32)
      at innerInvokeEventListeners ...

i apply beforeEach and change mockimplementationOnce > mockResolvedValueOnce, mockRejectedValueOnce

But the problem is that failure appears intermittently as the pages(test) change.

What I'm curious about the error message is... i did mocking A function(like fetchAuthMail.. fetchApiKeyUpdate) of in a request.

So I don't know why the error message is coming from axios even though I don't think I actually put a request in the test.


Solution

  • You should not use mockImplementation because the function returns a Promise. Just use mockResolvedValue and mockRejectedValue to make it return a Promise.

    Moreover, you should separate mocks with some describe (one for each use case) and add mocks into beforeAll / beforeEach functions.

    Personnally, I just tested it, I kept your @api/user.ts (I just added axios import) and by using these files it worked (100% working, never failing) :

    index.ts

    import * as api from '@api/user';
    
    export const main = async () => {
      const response = await api.fetchApiKeyUpdate({
        exchange: 'fake-exchange',
        apiKey: 'fake-apiKey',
        secretKey: 'fake-secretKey',
        passphrase: 'fake-passphrase',
        otpCode: 'fake-otpCode',
      });
      return response.user;
    };
    

    index.spec.ts

    import { main } from './index';
    
    import * as api from '@api/user';
    jest.mock('@api/user');
    
    jest.mock('axios', () => ({
      post: jest.fn().mockImplementation(() => {
        console.error('axios should be mocked !'); // If some logs are shown, it means that one axios request was not mocked.
      }),
    }));
    
    describe('UT main tests', () => {
      describe('error', () => {
        beforeEach(() => {
          (api.fetchApiKeyUpdate as jest.Mock).mockRejectedValue({ response: { data: { code: 'EX_INVALID_APIKEY_2015' } } });
        });
    
        it('should rejects but not throw', async () => {
          // Note that if you want to use .toThrow() method, you should set in mockRejectedValue an Error
          await expect(main()).rejects.toStrictEqual({ response: { data: { code: 'EX_INVALID_APIKEY_2015' } } });
        });
      });
    
      describe('error thrown', () => {
        beforeEach(() => {
          (api.fetchApiKeyUpdate as jest.Mock).mockRejectedValue(Error('FAKE ERROR'));
        });
    
        it('should rejects and throw', async () => {
          // All working
          await expect(main()).rejects.toThrow(Error);
          await expect(main()).rejects.toThrowError('FAKE ERROR');
          await expect(main()).rejects.toThrowError(Error('FAKE ERROR'));
        });
      });
    
    
      describe('success', () => {
        const mockResponse = {
          status: 'success',
          user: {
            id: '99b1231230',
            country: 'KR',
            language: 'ko',
          },
        };
    
        beforeEach(() => {
          (api.fetchApiKeyUpdate as jest.Mock).mockResolvedValue(mockResponse);
        });
    
        it('should return user', async () => {
          const response = await main();
          expect(response).toStrictEqual(mockResponse.user);
        });
      });
    });
    

    EDIT : I added a mock of axios post directly, you should check if an error log appears on your console. If so, it means you forgot to mock some axios calls.