【问题标题】:Jest Testing an asynchronous image upload in ReactJest 在 React 中测试异步图片上传
【发布时间】:2021-10-19 09:50:54
【问题描述】:

我正在尝试测试一组在用户上传文件后异步运行的函数。这些函数都在一个名为 UploadImageModal 的 React 组件中。这些函数是由 this on change 函数触发的:

const onChange = async () => {
  try {
    const file = document.querySelector('#upload-profile-img-dialog')
      .files[0];
    if (file) {
      const fileData = {
        mimeType: file.type,
        base64: await getBase64(file),
        file
      };

      await hasMinPicDimension(fileData);
      setPicFile(fileData);
    }
  } catch (error_) {
    setError(error_.message);
  }
};

这个 on change 函数然后调用这个 hasMinPicDimensions 函数:

const hasMinPicDimension = ({ mimeType, base64, file }) =>
new Promise((resolve, reject) => {
  const image = new Image();
  image.src = `data:${mimeType};base64,${base64}`;
  image.addEventListener('load', () => {
    if (!['image/gif', 'image/jpeg', 'image/png'].includes(mimeType)) {
      return reject(new Error('Image is not a supported type'));
    }

    if (image.height >= 151 && image.width >= 151) {
      return resolve({ mimeType, base64, file });
    }

    return reject(new Error('Image is too small'));
  });

  image.addEventListener('error', () => {
    reject(new Error('File is not an image'));
  });
});

在该函数解决了它的承诺之后,setPicFile,它是一个反应状态变化,它改变了一个组件 src 以拥有图像的数据 URI。 这是图片上传输入组件:

<Input
          id="upload-profile-img-dialog"
          name="profileImage"
          type="file"
          accept="image/gif,image/jpeg,image/png"
          onChange={onChange}
          sx={{ visibility: 'hidden' }}
          data-testid="fileinput"
        />

我已经尝试通过仅使用 Jest 来模拟图像上传来测试此功能 - 但无论上面的功能是什么都不会触发,因为 Jest 不会等待承诺解决。

有人对我如何使用 Jest 执行此操作有任何建议吗?如果我也需要使用enyzme,那很好。

【问题讨论】:

    标签: reactjs testing jestjs enzyme react-testing-library


    【解决方案1】:

    我们测试组件,模拟组件依赖的其他模块以及模块导出的功能。

    例如

    index.jsx:

    import React, { useState } from 'react';
    import { getBase64, hasMinPicDimension } from './helpers';
    
    export function UploadImageModal() {
      const [error, setError] = useState('');
      const [picFile, setPicFile] = useState();
    
      const onChange = async () => {
        try {
          const file = document.querySelector('#upload-profile-img-dialog').files[0];
          if (file) {
            const fileData = {
              mimeType: file.type,
              base64: await getBase64(file),
              file,
            };
    
            await hasMinPicDimension(fileData);
            setPicFile(fileData);
          }
        } catch (error_) {
          setError(error_.message);
        }
      };
    
      if (error) return <p>{error}</p>;
    
      // I use console.log to simulate using picFile
      console.log('picFile: ', picFile);
    
      return (
        <input
          id="upload-profile-img-dialog"
          name="profileImage"
          type="file"
          accept="image/gif,image/jpeg,image/png"
          onChange={onChange}
          data-testid="fileinput"
        />
      );
    }
    

    helpers.js:

    export const hasMinPicDimension = async ({ mimeType, base64, file }) =>
      new Promise((resolve, reject) => {
        const image = new Image();
        image.src = `data:${mimeType};base64,${base64}`;
        image.addEventListener('load', () => {
          if (!['image/gif', 'image/jpeg', 'image/png'].includes(mimeType)) {
            return reject(new Error('Image is not a supported type'));
          }
    
          if (image.height >= 151 && image.width >= 151) {
            return resolve({ mimeType, base64, file });
          }
    
          return reject(new Error('Image is too small'));
        });
    
        image.addEventListener('error', () => {
          reject(new Error('File is not an image'));
        });
      });
    
    export async function getBase64(file) {
      console.log('Your getBase64 real implementation');
    }
    

    index.test.jsx:

    import React from 'react';
    import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
    import { UploadImageModal } from './';
    import { getBase64, hasMinPicDimension } from './helpers';
    
    jest.mock('./helpers');
    
    describe('68818166', () => {
      afterEach(() => {
        jest.restoreAllMocks();
      });
      afterAll(() => {
        jest.resetAllMocks();
      });
      test('should upload file', async () => {
        const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
        jest.spyOn(document, 'querySelector').mockReturnValueOnce({ files: [file] });
        const logSpy = jest.spyOn(console, 'log');
        getBase64.mockResolvedValueOnce('mocked file base64 string');
        hasMinPicDimension.mockImplementationOnce((file) => file);
        render(<UploadImageModal />);
        fireEvent.change(screen.queryByTestId('fileinput'));
        await waitFor(() =>
          expect(logSpy).toBeCalledWith('picFile: ', {
            mimeType: 'image/png',
            base64: 'mocked file base64 string',
            file,
          })
        );
      });
    
      test('should handle error if file is invalid', async () => {
        const file = new File(['(⌐□_□)'], 'chucknorris.png', { type: 'image/png' });
        jest.spyOn(document, 'querySelector').mockReturnValueOnce({ files: [file] });
        getBase64.mockResolvedValueOnce('mocked file base64 string');
        hasMinPicDimension.mockRejectedValueOnce(new Error('Image is too small'));
        render(<UploadImageModal />);
        await act(async () => {
          fireEvent.change(screen.queryByTestId('fileinput'));
        });
        await waitFor(() => {
          expect(screen.findByText(/Image is too small/)).toBeTruthy();
        });
      });
    });
    

    测试结果:

      console.log
        picFile:  undefined
    
          at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)
    
      console.log
        picFile:  {
          mimeType: 'image/png',
          base64: 'mocked file base64 string',
          file: File {}
        }
    
          at console.<anonymous> (node_modules/jest-mock/build/index.js:845:25)
    
      console.log
        picFile:  undefined
    
          at UploadImageModal (examples/68818166/index.jsx:29:11)
    
     PASS  examples/68818166/index.test.jsx
      68818166
        ✓ should upload file (105 ms)
        ✓ should handle error if file is invalid (10 ms)
    
    ------------|---------|----------|---------|---------|-------------------
    File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ------------|---------|----------|---------|---------|-------------------
    All files   |   61.76 |       30 |   33.33 |      60 |                   
     helpers.js |   18.75 |        0 |       0 |   14.29 | 2-18,23           
     index.jsx  |     100 |       75 |     100 |     100 | 11                
    ------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        2.409 s, estimated 10 s
    

    【讨论】:

      猜你喜欢
      • 2019-10-30
      • 2020-01-20
      • 2020-08-04
      • 1970-01-01
      • 2018-11-12
      • 1970-01-01
      • 2020-03-15
      • 1970-01-01
      • 2022-11-10
      相关资源
      最近更新 更多