Jest модульный тест на (‘ошибка’) createWriteStream

#node.js #typescript #unit-testing #jestjs #nestjs

#node.js #машинопись #модульное тестирование #jestjs #nestjs

Вопрос:

Я использую Nestjs и написал функцию ниже, которая получает файл из post-запроса и сохраняет его в папке в моем проекте. Моя проблема в том, что я не уверен, как протестировать on('error') ветку.

функция для модульного тестирования.

   saveFile({ createReadStream, filename }: FileUpload): Promise<boolean> {
    return new Promise(async (resolve, reject) => {
      createReadStream().pipe(
        createWriteStream(join(process.cwd(), `apps/mull-api/uploads/${filename}`))
          .on('finish', () => resolve(true))
          .on('error', () => {
            console.log(createReadStream);
            reject(false);
          })
      );
    });
  }
  

Как я тестирую on('finish') ветку

   it('should save file', async () => {
    const returnedFile = await service.saveFile(mockFile);
    expect(returnedFile).toBe(true);
  });
  

Вот как выглядит мой макет файла. Я попытался предоставить макет файла с пустым именем, но он выдал ошибку.

 export const mockFile: FileUpload = {
  filename: 'zoro',
  mimetype: 'image/jpeg',
  encoding: '7bit',
  createReadStream(): ReadStream {
    return fs.createReadStream(join(process.cwd(), `apps/mull-api/uploads/mock-upload/zoro`));
  },
};
  

Комментарии:

1. Вы ищете модульное тестирование или интеграционное тестирование?

2. почему вы используете асинхронную функцию внутри promise?

3. @slideshowp2 Я пытаюсь выполнить модульное тестирование этой функции. Мне нужно достичь порога 80% покрытия в моем проекте

4. @SyedMisharNewaz Я следовал указаниям гида, и вот как это было сделано там stephen-knutter.github.io/2020-02-07-nestjs-graphql-file-upload

Ответ №1:

Мы можем издеваться createWriteStream .on('finish') и .on('error') использовать методы mockImplementation() . И запускаем эти два события в функции макетной реализации самостоятельно.

'finish' Событие handler в функции макетной реализации является () => resolve(true) ; 'error' Событие handler в функции макетной реализации является () => reject(false);

См. Макет реализации и пример ниже:

 const myMockFn = jest.fn(cb => cb(null, true));

myMockFn((err, val) => console.log(val));
// > true
  

index.ts :

 import { createWriteStream, ReadStream } from 'fs';
import { join } from 'path';

export interface FileUpload {
  filename: string;
  mimetype: string;
  encoding: string;
  createReadStream(): ReadStream;
}

export class FileService {
  public saveFile({ createReadStream, filename }: FileUpload): Promise<boolean> {
    return new Promise(async (resolve, reject) => {
      createReadStream().pipe(
        createWriteStream(join(process.cwd(), `apps/mull-api/uploads/${filename}`))
          .on('finish', () => resolve(true))
          .on('error', () => {
            reject(false);
          }),
      );
    });
  }
}
  

index.test.ts :

 import { FileService, FileUpload } from './';
import { createWriteStream, WriteStream } from 'fs';
import { mocked } from 'ts-jest/utils';

jest.mock('fs');

describe('64485251', () => {
  afterAll(() => {
    jest.resetAllMocks();
    jest.clearAllMocks();
  });
  it('should save file', async () => {
    const mockReadStream = { pipe: jest.fn() };
    const mockFile: FileUpload = {
      filename: 'zoro',
      mimetype: 'image/jpeg',
      encoding: '7bit',
      createReadStream: jest.fn().mockReturnValueOnce(mockReadStream),
    };
    const mockWriteStream = {
      on: jest.fn().mockImplementation(function(this, event, handler) {
        if (event === 'finish') {
          handler();
        }
        return this;
      }),
    };
    mocked(createWriteStream).mockReturnValueOnce((mockWriteStream as unknown) as WriteStream);
    const service = new FileService();
    const actual = await service.saveFile(mockFile);
    expect(mockFile.createReadStream).toBeCalledTimes(1);
    expect(mockReadStream.pipe).toBeCalledTimes(1);
    expect(createWriteStream).toBeCalledWith(expect.stringContaining('apps/mull-api/uploads/zoro'));
    expect(mockWriteStream.on).toBeCalledWith('finish', expect.any(Function));
    expect(mockWriteStream.on).toBeCalledWith('error', expect.any(Function));
    expect(actual).toBeTruthy();
  });

  it('should handle error if save file failed', async () => {
    const mockReadStream = { pipe: jest.fn() };
    const mockFile: FileUpload = {
      filename: 'zoro',
      mimetype: 'image/jpeg',
      encoding: '7bit',
      createReadStream: jest.fn().mockReturnValueOnce(mockReadStream),
    };
    const mockWriteStream = {
      on: jest.fn().mockImplementation(function(this, event, handler) {
        if (event === 'error') {
          handler();
        }
        return this;
      }),
    };
    mocked(createWriteStream).mockReturnValueOnce((mockWriteStream as unknown) as WriteStream);
    const service = new FileService();
    await expect(service.saveFile(mockFile)).rejects.toEqual(false);
    expect(mockFile.createReadStream).toBeCalledTimes(1);
    expect(mockReadStream.pipe).toBeCalledTimes(1);
    expect(createWriteStream).toBeCalledWith(expect.stringContaining('apps/mull-api/uploads/zoro'));
    expect(mockWriteStream.on).toBeCalledWith('finish', expect.any(Function));
    expect(mockWriteStream.on).toBeCalledWith('error', expect.any(Function));
  });
});
  

результат модульного теста:

  PASS  src/stackoverflow/64485251/index.test.ts (10.201s)
  64485251
    ✓ should save file (6ms)
    ✓ should handle error if save file failed (3ms)

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        11.344s
  

Комментарии:

1. Эй, извините, но я не понимаю в вашем тестовом примере на ошибку, что на самом деле вызывает error событие?

2. @AriaGroult обновил ответ с дополнительными объяснениями.