Насмешливый насос node_module с различными реализациями с использованием jest - Typescript - PullRequest
0 голосов
/ 04 апреля 2020

Я пытаюсь реализовать gcloud-storage с nodejs и протестировать их с помощью машинописи. Это мой настоящий класс

Пожалуйста, не рассматривайте реализацию ведения журналов на данный момент. Хранилище аутентифицируется внешним вызовом службы -

const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket ();

, и файл, который я хочу сохранить в gcloud, управляется используя потоки, с модулем насоса

export const uploadEnvFiles = async (env_name: string) => {

        const LOGGER: pino.Logger = PinoLoggerServiceInstance.getLogger(__filename);

        return new Promise(async (res, rej) => {
            const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket();

            const bucketToUpload = GCLOUD_ENV_STR_BUCKET_NAME;
            let uploadLocalFilePath;
            let destinationBucketPath;
            if (!AppUtilServiceInstance.isNullOrUndefined(env_name)) {
                uploadLocalFilePath = ENV_NAME_DEV === env_name ? GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH : GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH;
                destinationBucketPath = ENV_NAME_DEV === env_name ? GCLOUD_DATABASE_BUCKET_DEV : GCLOUD_DATABASE_BUCKET_PROD;
            }
            LOGGER.info('after authentication');
            pump(
                fs.createReadStream(uploadLocalFilePath),
                str
                    .bucket(bucketToUpload)
                    .file(destinationBucketPath)
                    .createWriteStream({
                        gzip: true,
                        public: true,
                        resumable: true,
                    })
            )
                .on('error', (err) => {
                    LOGGER.error('Error occured in uploading:', err);
                    rej({ status: 'Error', error: err, code: 500 });
                })
                .on('finish', () => {
                    LOGGER.info('Successfully uploaded the file');
                    res({ status: 'Success', code: 201, error: null });
                });
        });
    };

Теперь есть возможность завершения или сбоя потока, и я хотел проверить оба. Я могу смоделировать модуль pump npm в целом с помощью jest.mock, подобного этому, поднятому вверху, перед объявлениями любого набора тестов.

jest.mock('pump', () =>
    jest.fn().mockImplementation(() => {
        const readStream = fs.createReadStream(
            path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt')
        );
        const writeStream = fs.createWriteStream(
            path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt')
        );
        return readStream.pipe(writeStream);
    })
);

Так что выше приведена реализация рабочего сценария, где я передал существующий файл в выходной поток и возвратил поток, заставляя работать макет насоса. Вот мой тест spe c file

const globalAny: any = global;

describe('Test suite for bucket functionality', () => {
    beforeEach(() => {
        jest.restoreAllMocks();

    });
    afterAll(() => {
        jest.clearAllMocks();
        jest.restoreAllMocks();
        jest.resetAllMocks();

    });

    test('test upload - make the actual call', async (done) => {
        // to make sure that mock fs doesnt affect the gcloud authentication, this is a MUST
        const createGcloudAuthenticationBucketSpy = jest
            .spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
            .mockImplementation(() => {
                return new Storage();
            });
        const res = BucketOperations.uploadEnvFiles(globalAny.ENV_JEST);
        await expect(res).resolves.toBeDefined();
        expect(createGcloudAuthenticationBucketSpy).toHaveBeenCalledTimes(1);
        done();
    });

});

Теперь это работает с проверенным вызовом насоса. Но я хотел протестировать сценарий, в котором поток также выдает ошибку в той же спецификации c. Есть ли возможность перезаписать mockImplementation в другом тесте spe c. Поскольку это модуль npm, я написал вверху jest.mock (), который будет служить макетом для всего набора тестов, но не уверен, как его перезаписать. Я пытался в течение прошлых 3 дней и не мог понять это. Есть ли способ достичь этого?

1 Ответ

0 голосов
/ 09 апреля 2020

Вот решение для модульного тестирования, использующее jest.mock (имя_модуля, фабрика, параметры) и jest.spyOn (объект, имя-метода) .

bucketOperations.ts:

import fs from 'fs';
import pump from 'pump';
import { GcloudAuthenticationInstance } from './gcloudAuthenticationInstance';
import { AppUtilServiceInstance } from './appUtilServiceInstance';

const {
  GCLOUD_ENV_STR_BUCKET_NAME,
  GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH,
  GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH,
  GCLOUD_DATABASE_BUCKET_DEV,
  GCLOUD_DATABASE_BUCKET_PROD,
  ENV_NAME_DEV,
} = process.env;

export const uploadEnvFiles = async (env_name: string) => {
  return new Promise(async (res, rej) => {
    const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket();

    const bucketToUpload = GCLOUD_ENV_STR_BUCKET_NAME;
    let uploadLocalFilePath;
    let destinationBucketPath;
    if (!AppUtilServiceInstance.isNullOrUndefined(env_name)) {
      uploadLocalFilePath =
        ENV_NAME_DEV === env_name ? GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH : GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH;
      destinationBucketPath = ENV_NAME_DEV === env_name ? GCLOUD_DATABASE_BUCKET_DEV : GCLOUD_DATABASE_BUCKET_PROD;
    }
    console.info('after authentication');
    pump(
      fs.createReadStream(uploadLocalFilePath),
      str
        .bucket(bucketToUpload)
        .file(destinationBucketPath)
        .createWriteStream({
          gzip: true,
          public: true,
          resumable: true,
        }),
    )
      .on('error', (err) => {
        console.error('Error occured in uploading:', err);
        rej({ status: 'Error', error: err, code: 500 });
      })
      .on('finish', () => {
        console.info('Successfully uploaded the file');
        res({ status: 'Success', code: 201, error: null });
      });
  });
};

appUtilServiceInstance.ts:

const AppUtilServiceInstance = {
  isNullOrUndefined: (env_name) => typeof env_name === 'undefined',
};

export { AppUtilServiceInstance };

gcloudAuthenticationInstance.ts:

const GcloudAuthenticationInstance = {
  createGcloudAuthenticationBucket: () => {
    const storage = {
      bucket(name) {
        return this;
      },
      file(filename) {
        return this;
      },
      createWriteStream(options) {
        return 'write stream';
      },
    };
    return storage;
  },
};

export { GcloudAuthenticationInstance };

bucketOperations.test.ts:

import pump from 'pump';
import fs from 'fs';
import { GcloudAuthenticationInstance } from './gcloudAuthenticationInstance';

jest.mock('pump', () => {
  const mPump = { on: jest.fn() };
  return jest.fn(() => mPump);
});

describe('61031410', () => {
  let originalEnv;
  beforeEach(() => {
    originalEnv = process.env;
  });
  afterEach(() => {
    process.env = originalEnv;
    jest.restoreAllMocks();
  });
  it('should upload file correctly', async () => {
    process.env.ENV_NAME_DEV = 'dev';
    process.env.GCLOUD_ENV_STR_BUCKET_NAME = 'bucket-dev';
    process.env.GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH = 'dev';
    process.env.GCLOUD_DATABASE_BUCKET_DEV = 'bucket-dev-db';
    const BucketOperations = require('./bucketOperations');
    const createReadStreamSpy = jest.spyOn(fs, 'createReadStream').mockReturnValueOnce('rs' as any);
    const mStorage: any = {
      bucket: jest.fn().mockReturnThis(),
      file: jest.fn().mockReturnThis(),
      createWriteStream: jest.fn().mockReturnValueOnce('ws'),
    };
    const infoSpy = jest.spyOn(console, 'info');
    const createGcloudAuthenticationBucketSpy = jest
      .spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
      .mockReturnValueOnce(mStorage);
    pump().on.mockImplementation(function(this: any, event, callback) {
      if (event === 'finish') {
        callback();
      }
      return this;
    });
    const actual = await BucketOperations.uploadEnvFiles('dev');
    expect(actual).toEqual({ status: 'Success', code: 201, error: null });
    expect(createGcloudAuthenticationBucketSpy).toBeCalledTimes(1);
    expect(pump).toBeCalledWith('rs', 'ws');
    expect(createReadStreamSpy).toBeCalledWith('dev');
    expect(mStorage.bucket).toBeCalledWith('bucket-dev');
    expect(mStorage.file).toBeCalledWith('bucket-dev-db');
    expect(mStorage.createWriteStream).toBeCalledWith({ gzip: true, public: true, resumable: true });
    expect(infoSpy.mock.calls[0]).toEqual(['after authentication']);
    expect(infoSpy.mock.calls[1]).toEqual(['Successfully uploaded the file']);
  });
  it('should handle the error if upload file failure', () => {
    // TODO: you can do this like above
  });
});

результаты модульного теста с отчетом о покрытии:

 PASS  stackoverflow/61031410/bucketOperations.test.ts (7.94s)
  61031410
    ✓ should upload file correctly (69ms)
    ✓ should handle the error if upload file failure

  console.info node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
    after authentication

  console.info node_modules/jest-environment-enzyme/node_modules/jest-mock/build/index.js:866
    Successfully uploaded the file

---------------------------------|---------|----------|---------|---------|-------------------
File                             | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
---------------------------------|---------|----------|---------|---------|-------------------
All files                        |   80.56 |       50 |   54.55 |   79.41 |                   
 appUtilServiceInstance.ts       |     100 |      100 |     100 |     100 |                   
 bucketOperations.ts             |   92.31 |       50 |   83.33 |   91.67 | 40,41             
 gcloudAuthenticationInstance.ts |   28.57 |      100 |       0 |   28.57 | 3,5,8,11,14       
---------------------------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        9.247s

исходный код: https://github.com/mrdulin/react-apollo-graphql-starter-kit/tree/master/stackoverflow/61031410

...