Jest mocking google-cloud/storage typescript
Asked Answered
H

1

6

I have been trying to mock the @google-cloud/storage for my implementation so that I could test it without having to hit the cloud-storge in gcp and so far it has all been in vain I have tried to mock the node_module scope folder using the jest doc and that didnt work out Hence I tried using below

This is my implementation class

import { GcloudAuthenticationInstance } from '../common/services/gcloud.authentication';
import * as fs from 'fs';
import pump from 'pump';
import pino from 'pino';
import * as _ from 'lodash';

import {
    ENV_NAME_DEV,
    GCLOUD_DATABASE_BUCKET_DEV,
    GCLOUD_DATABASE_BUCKET_PROD,
    GCLOUD_ENV_STR_BUCKET_NAME,
    GCLOUD_STORED_FILE_NAME_DEV,
    GCLOUD_STORED_FILE_NAME_PROD,
    GCLOUD_UPLOAD_FILE_DEV_LOCAL_PATH,
    GCLOUD_UPLOAD_FILE_PROD_LOCAL_PATH,
} from '../common/util/app.constants';
import { PinoLoggerServiceInstance } from '../common/services/pino.logger.service';
import { AppUtilServiceInstance } from '../common/services/app.util.service';
export const uploadEnvFiles = async (env_name: string) => {
    const LOGGER: pino.Logger = PinoLoggerServiceInstance.getLogger(__filename);

    return new Promise(async (res, rej) => {
       // This just returns the Storage() instance with keyFileName and projectID
        //of google cloud console being set so authentication takes place
        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 });
            });
    });
};

export const downloadEnvFiles = async (env_name): Promise<any> => {
    const LOGGER: pino.Logger = PinoLoggerServiceInstance.getLogger(__filename);

    return new Promise(async (res, rej) => {
        const str = GcloudAuthenticationInstance.createGcloudAuthenticationBucket();
        try {
            const [files] = await str.bucket(GCLOUD_ENV_STR_BUCKET_NAME).getFiles();

            const filteredFile =
                ENV_NAME_DEV === env_name
                    ? _.find(files, (file) => {
                        c
                            return file.name.includes(GCLOUD_STORED_FILE_NAME_DEV);
                      })
                    : _.find(files, (file) => {

                            return file.name.includes(GCLOUD_STORED_FILE_NAME_PROD);
                      });
            res({
                status: 'Success',
                code: 200,
        error: null,
                stream: str
                    .bucket(GCLOUD_ENV_STR_BUCKET_NAME)
                    .file(filteredFile.name)
                    .createReadStream()
            });
        } catch (err) {
            LOGGER.error('Error in retrieving files from gcloud:'+err);
            rej({ status: 'Error', error: err, code: 500 });
        }
    });
};

This is my jest ts

bucket.operations.int.spec.ts

I've tried to include the mock inline

 import { GcloudAuthenticationInstance } from '../common/services/gcloud.authentication';
const { Storage } = require('@google-cloud/storage');
const { Bucket } = require('@google-cloud/storage');
import { File } from '@google-cloud/storage';
import { mocked } from 'ts-jest/utils'
const fs = require('fs');
import * as path from 'path';
import pump from 'pump';
import * as BucketOperations from './bucket.operations';
import { GCLOUD_ENV_STR_BUCKET_NAME } from '../common/util/app.constants';
const { PassThrough } = require('stream');
const fsMock = jest.mock('fs');

// Here we are trying to mock pump with a function returned
// since pump () is the actual fucntion, we are mocking the function to return a value
// which is just a value of "on" eventlistener.. so we indicate that this will be substituted
// with another mocked function
jest.genMockFromModule('@google-cloud/storage');
jest.mock('@google-cloud/storage', () => {

  const mockedFile = jest.fn().mockImplementation(() => {
    return {
      File: jest.fn().mockImplementation(() => {
        return {
          name: 'dev.txt',
          createReadStream: jest
            .fn()
            .mockReturnValue(
              fs.createReadStream(
                path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt')
              )
            ),
          createWriteStream: jest
            .fn()
            .mockReturnValue(
              fs.createWriteStream(
                path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt')
              )
            )
        };
      })
    };
  });

  const mockedBUcket = jest.fn().mockImplementation(() => {
    return {
      Bucket: jest.fn().mockImplementation(() => {
        return {
          constructor: jest.fn().mockReturnValue('test-bucket'),
          getFiles: jest.fn().mockReturnValue([mockedFile])
        }
      })
    }
  });


  return {
    Storage: jest.fn().mockImplementation(() => {
      return {
        constructor: jest.fn().mockReturnValue('test-storage'),
        bucket: mockedBUcket,
        file: mockedFile,
        createWriteStream: jest.fn().mockImplementation(() =>
          fs.createWriteStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt')))
      };
    })
  };
});

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

describe('Test suite for testing bucket operations', () => {
  const mockedStorage = mocked(Storage, true);
  const mockeddFile = mocked(File, true);
  const mockeddBucket = mocked(Bucket, true);
  function cancelCloudStorageMock() {
    //mockCloudStorage.unmock('@google-cloud/storage');
    mockedStorage.mockClear();
    mockeddBucket.mockClear();
    mockeddFile.mockClear();
    jest.unmock('@google-cloud/storage');
    jest.requireActual('@google-cloud/storage');
  }

  function cancelFsMock() {
    jest.unmock('fs');
    jest.requireActual('fs');
  }

  afterEach(() => {
    jest.clearAllMocks();
    //jest.restoreAllMocks();
  });
  test('test for uploadfiles - success', async (done) => {
    cancelFsMock();
    pump().on = jest.fn(function(this: any, event, callback) {
      if (event === 'finish') {
        callback();
      }
      return this;
    });
    const actual = await BucketOperations.uploadEnvFiles('dev');

    expect(actual).toEqual(
      expect.objectContaining({
        status: 'Success',
        code: 201,
      })
    );
    done();
  });


  test('test downloadEnvFiles  - success', async (done) => {
    jest.unmock('fs');

    const fMock = (File.prototype.constructor = jest.fn().mockImplementation(() => {
      return {
        storage: new Storage(),
        bucket: 'testBucket',
        acl: 'test-acl',
        name: 'dev.txt',
        parent: 'parent bucket',
      };
    }));

    const bucketGetFilMock = (Bucket.prototype.getFiles = jest.fn().mockImplementation(() => {
      return [fMock];
    }));

    // Get files should be an array of File from google-cloud-storage
    //Bucket.prototype.getFiles = jest.fn().mockReturnValue([mockedFsConstructor]);

    //Storage.prototype.bucket = jest.fn().mockReturnValue(new Storage());

    const mockReadable = new PassThrough();
    const mockWritable = new PassThrough();

    jest.spyOn(fs, 'createReadStream').mockReturnValue(
      fs.createWriteStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt'))
    );
    await BucketOperations.downloadEnvFiles('dev');

    done();
  });
});

This is the exception I end up with. Upon debugging I see that the mocked instances are trying to execute, but it doesn't execute the file method in Storage mock. This is not available in @google-cloud/storage but I did try to mock it. Is there a way to mock just the usage of google-cloud/storage using jest?

EDIT: Here is the exception:

TypeError: str.bucket(...).file is not a function

    at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:37:6
    at Generator.next (<anonymous>)
    at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:8:71
    at new Promise (<anonymous>)
    at Object.<anonymous>.__awaiter (/home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:4:12)
    at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:22:40
    at new Promise (<anonymous>)
    at /home/vijaykumar/Documents/Code/Nestjs/cloud-storage-app/src/gcloud/bucket.operations.ts:22:9
Harbison answered 15/4, 2020 at 7:28 Comment(2)
Do you get an exception with that code? You did mentioned it after the last code block, if yes, please share it. Also I have found this community question that may be of use to solving you issue.Brokerage
@ralemos: Apologies. I've added the exception in the EDIT sectionHarbison
H
14

Thanks to @ralemos. I was able to find the answer on how I mocked Here is the complete implementation.

I've added a few more test stories as well

So jest.mock() esp the @google-cloud/storage modules, needs to be mocked in a different way. The Bucket of the Storage has all the details of the files in gcp storage, so that needs to be mocked first, I also mocked the File (this is of type @google-cloud/storage). Now I added the mockedFile to the mockedBucket and from there to the mockedStorage. I've also added all the methods and properties and implemented a mock for all of them.

There is a lodash node_module usage in my test file, so I mocked that implementation as well. Now everything works fine.

import { GcloudAuthenticationInstance } from '../common/services/gcloud.authentication';
const { Storage } = require('@google-cloud/storage');
const fs = require('fs');
import * as path from 'path';
import pump from 'pump';
import * as BucketOperations from './bucket.operations';
const { PassThrough } = require('stream');
const fsMock = jest.mock('fs');

const mockedFile = {
  name: 'dev.txt',
  createWriteStream: jest.fn().mockImplementation(() => {
    return fs.createWriteStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-write.txt'));
  }),
  createReadStream: jest.fn().mockImplementation(() => {
    return fs.createReadStream(path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt'));
  }),
};
jest.mock('lodash', () => {
  return {
    find: jest.fn().mockImplementation(() => {
      return mockedFile;
    })
  };

});
const mockedBucket = {
  file: jest.fn(() => mockedFile),
  getFiles: jest.fn().mockImplementation(() => {
    const fileArray = new Array();
    fileArray.push(mockedFile);
    return fileArray;
  })
};

const mockedStorage = {
  bucket: jest.fn(() => mockedBucket)
};


jest.mock('@google-cloud/storage', () => {
  return {
    Storage: jest.fn(() => mockedStorage)
  };
});

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

describe('Test suite for testing bucket operations', () => {

  function cancelCloudStorageMock() {
    jest.unmock('@google-cloud/storage');
    jest.requireActual('@google-cloud/storage');
  }

  function cancelFsMock() {
    jest.unmock('fs');
    jest.requireActual('fs');
  }

  afterEach(() => {
    jest.clearAllMocks();
    //jest.restoreAllMocks();
  });
  test('test for uploadfiles - success', async (done) => {

    pump().on = jest.fn(function(this: any, event, callback) {
      if (event === 'finish') {
        callback();
      }
      return this;
    });
    const actual = await BucketOperations.uploadEnvFiles('dev');

    expect(actual).toEqual(
      expect.objectContaining({
        status: 'Success',
        code: 201,
      })
    );
    done();
  });


  test('test downloadEnvFiles  - success', async (done) => {
    jest.unmock('fs');
    const downloadRes =  await BucketOperations.downloadEnvFiles('dev');
    expect(downloadRes).toBeDefined();
    expect(downloadRes).toEqual(expect.objectContaining({code:200, status: 'Success'}));
    done();
  });

  test('test for uploadfiles- failure', async (done) => {
    cancelCloudStorageMock();
    const bucketStorageSpy = jest
      .spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
      .mockImplementation(() => {
        return new Storage({
          projectId: 'testId',
          keyFilename: path.resolve(process.cwd(), './tests/cloud-storage/sample-read.txt'),
          scopes: ['testScope'],
          autoRetry: false,
        });
      });

    const mockReadable = new PassThrough();
    const mockWritable = new PassThrough();
    fs.createWriteStream = jest.fn().mockReturnValue(mockWritable);
    fs.createReadStream = jest.fn().mockReturnValue(mockReadable);
    pump().on = jest.fn(function(this: any, event, callback) {
      if (event === 'error') {
        callback();
      }
      return this;
    });
    const actual = BucketOperations.uploadEnvFiles('prod');
    expect(actual).rejects.toEqual(
      expect.objectContaining({
        status: 'Error',
        code: 500,
      })
    );
    expect(bucketStorageSpy).toHaveBeenCalledTimes(1);
    done();
  });

  test('test download - make the actual call - rej with auth error', async (done) => {
    cancelCloudStorageMock();
    console.dir(Storage);
    const mockReadable = new PassThrough();
    const mockWritable = new PassThrough();
    fs.createWriteStream = jest.fn().mockReturnValue(mockWritable);
    fs.createReadStream = jest.fn().mockReturnValue(mockReadable);
    const createGcloudAuthenticationBucketSpy = jest
      .spyOn(GcloudAuthenticationInstance, 'createGcloudAuthenticationBucket')
      .mockImplementation(() => {
        return new Storage();
      });
    try {
      await BucketOperations.downloadEnvFiles('dev');
    } catch (err) {
      expect(err.code).toBe(500);
      expect(err.status).toBe('Error');
    }

    expect(createGcloudAuthenticationBucketSpy).toHaveBeenCalledTimes(1);
    createGcloudAuthenticationBucketSpy.mockReset();
    done();
  });
});
Harbison answered 15/4, 2020 at 18:57 Comment(1)
Glad I was able to help :)Brokerage

© 2022 - 2025 — McMap. All rights reserved.