Skip to content

Instantly share code, notes, and snippets.

@nfarina
Last active November 23, 2023 15:50
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save nfarina/90ba99a5187113900c86289e67586aaa to your computer and use it in GitHub Desktop.
Save nfarina/90ba99a5187113900c86289e67586aaa to your computer and use it in GitHub Desktop.
Mock Google Cloud Storage for JS
//
// Quick & Dirty Google Cloud Storage emulator for tests. Requires
// `stream-buffers` from npm. Use it like this:
//
// `new MockStorage().bucket('my-bucket').file('my_file').createWriteStream()`
//
class MockStorage {
buckets: {[name: string]: MockBucket};
constructor() {
this.buckets = {};
}
bucket(name: string) {
return this.buckets[name] ||
(this.buckets[name] = new MockBucket(name));
}
}
class MockBucket {
name: string;
files: {[path: string]: MockFile};
constructor(name: string) {
this.name = name;
this.files = {};
}
file(path: string) {
return this.files[path] ||
(this.files[path] = new MockFile(path));
}
}
class MockFile {
path: string;
contents: Buffer;
metadata: Object;
constructor(path: string) {
this.path = path;
this.contents = new Buffer(0);
this.metadata = {};
}
get() {
return [this, this.metadata];
}
setMetadata(metadata: Object) {
const customMetadata = {...this.metadata.metadata, ...metadata.metadata};
this.metadata = {...this.metadata, ...metadata, metadata: customMetadata};
}
createReadStream() {
const streamBuffers = require('stream-buffers');
const readable = new streamBuffers.ReadableStreamBuffer();
readable.put(this.contents);
readable.stop();
return readable;
}
createWriteStream({metadata}: Object) {
this.setMetadata(metadata);
const streamBuffers = require('stream-buffers');
const writable = new streamBuffers.WritableStreamBuffer();
writable.on('finish', () => {
this.contents = writable.getContents();
});
return writable;
}
delete() {
return Promise.resolve();
}
}
@ferco0
Copy link

ferco0 commented Mar 2, 2020

add delete method to MockFile returning a resolved promise.

delete() {
  return Promise.resolve();
}

@nfarina
Copy link
Author

nfarina commented Mar 2, 2020

add delete method to MockFile returning a resolved promise.

Added, thanks!

@seclace
Copy link

seclace commented Mar 17, 2020

There is also good enough to add upload method to MockBucket

  upload(path: string, options: any) {
    return [this.file(path)];
  }

and add makePublic method to MockFile

  async makePublic() {
    return [
      { bucket: 'test-bucket', object: 'test-object' },
    ]
  }

@Ignaciojeria
Copy link

your code was help a lot :D

@nfarina
Copy link
Author

nfarina commented Jun 7, 2020

Glad to hear it!

@ferco0
Copy link

ferco0 commented Jul 25, 2020

hey man, you should publish an npm package of this, what you think? If you allow, i do it.

@nfarina
Copy link
Author

nfarina commented Jul 25, 2020

Sure I’m happy to turn this into a proper repo and npm package, if people are finding it useful.

@ferco0
Copy link

ferco0 commented Jul 25, 2020

Would be userful for me, please, do it :)

@uschtwill
Copy link

No package yet? 😅

@shotah
Copy link

shotah commented Mar 7, 2023

TS Example I've been using from the example above, I wanted to contribute back as it was a big help.

/* eslint-disable @typescript-eslint/no-unused-vars */
//
// Quick & Dirty Google Cloud Storage emulator for tests. Requires
// `stream-buffers` from npm. Use it like this:
//
// `new MockStorage().bucket('my-bucket').file('my_file').createWriteStream()`
//
import type { CreateWriteStreamOptions, GetSignedUrlConfig } from '@google-cloud/storage';
import streamBuffers from 'stream-buffers';

export class MockStorage {
  public buckets: object;

  public constructor() {
    this.buckets = {};
  }

  public bucket(name: string): MockBucket {
    if (this.buckets[name] === undefined) {
      this.buckets[name] = new MockBucket(name);
    }
    return this.buckets[name];
  }
}

export class MockBucket {
  public name: string;

  public files: object;

  public constructor(name: string) {
    this.name = name;
    this.files = {};
  }

  public upload(name: string, options: any): MockFile[] {
    return [this.file(name)];
  }

  public file(name: string): MockFile {
    if (this.files[name] === undefined) {
      this.files[name] = new MockFile(name);
    }
    return this.files[name];
  }
}

interface Metadata {
  metadata?: object;
}

export class MockFile {
  public name: string;

  public path?: string;

  public contents: Buffer;

  public metadata: {
    metadata?: object;
  };

  public constructor(name: string, path?: string) {
    this.name = name;
    this.path = path;
    this.contents = Buffer.alloc(0);
    this.metadata = {};
  }

  public get(): [MockFile, any] {
    return [this, this.metadata];
  }

  public async delete(): Promise<void> {
    return Promise.resolve();
  }

  public exists(): [boolean, any] {
    return [true, this.metadata];
  }

  public setMetadata(metadata: Metadata): void {
    const customMetadata = { ...this.metadata.metadata, ...metadata.metadata };
    this.metadata = { ...this.metadata, ...metadata, metadata: customMetadata };
  }

  public async getSignedUrl(options?: GetSignedUrlConfig): Promise<string> {
    return Promise.resolve('https://example.com');
  }

  public createReadStream(): any {
    const readable = new streamBuffers.ReadableStreamBuffer();
    readable.put(this.contents);
    readable.stop();
    return readable;
  }

  public createWriteStream(options?: CreateWriteStreamOptions): any {
    const writable = new streamBuffers.WritableStreamBuffer();
    writable.on('finish', () => {
      this.contents = writable.getContents();
    });
    return writable;
  }
}

@ujwal-setlur
Copy link

Hi, how do you actually inject it into jest mocking? Thanks!

@nfarina
Copy link
Author

nfarina commented Mar 20, 2023

Hi, how do you actually inject it into jest mocking? Thanks!

I personally run all my Firebase calls through a single getFirebaseApp() sort of function. When running under test mode, it returns a mock App that stubs out the getters for mock Storage, Firestore, etc.

@shotah
Copy link

shotah commented Mar 20, 2023

Should be able to use https://jestjs.io/docs/mock-functions#mocking-modules, or https://www.npmjs.com/package/proxyquire. Both should be able to mock out the actual call to '@google-cloud/storage'

@ujwal-setlur
Copy link

I am making some progress with this approach:

function mockedStorage() {
  return class MockStorage {
    public buckets: MockBucketList;

    public constructor() {
      this.buckets = {};
    }

    public bucket(name: string): MockBucket {
      if (this.buckets[name] === undefined) {
        this.buckets[name] = new MockBucket(name);
      }
      return this.buckets[name];
    }
  };
}

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

@aldipermanaetikaputra
Copy link

Hi everyone! Just wanted to share that I recently published a small package to help mock @google-cloud/storage written in TypeScript. Check out mock-gcs: https://github.com/aldipermanaetikaputra/mock-gcs. Hope it helps!

@shotah
Copy link

shotah commented Apr 8, 2023

@aldipermanaetikaputra I have a Datastore one if you'd like that as well. I couldn't find this packages repo so I couldn't file issues or contribute back. https://www.npmjs.com/package/datastore-mock?activeTab=readme So I forked it to a script. Would be nice if it could get maintained and updated.

@nfarina
Copy link
Author

nfarina commented Apr 8, 2023

I have a rather-insane Firestore one as well :) actually works...but it only implements the "compat" API

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment