gcp-testing
v1.0.4
Published
Testing utilities for GCP
Downloads
8
Maintainers
Readme
Hello Everyone,
This package includes some utilities to do testing for your node project that uses firestore or google cloud storage and need some stubs!
Available stubs and how to use them
In some cases, you will need to have an online test, which is okay if you have a firebase testing project ready. But in most cases you don't actually need to make it online, that's why we're creating these stubs, to have the ability to emulate firestore functionalities offline. Currently, there exist these stubs:
firestoreGetStub
firestoreSetStub
firestoreAddStub
firestoreUpdateStub
firestoreDeleteStub
firestoreCollectionDocRefStub
firestoreCollectionDocRefStubData
firestoreTransactionUpdateStub
firestoreTransactionSetStub
firebaseAuthStub
Google Storage Buckets Stub
logger
Important Note : all stubs are created using sinon.js, for more examples and full API please refer to sinon stubs.
Firestore Get Stub
This function provides an Object with these properties :
queryGetStub
stub forcollection("...").where(...).get
docGetStub
stub forcollection("...").doc(...).get
transactionGetStub
stub fortransaction.get({...})
restore
function that calls restore for all the stubs above- note that: stub.restore() remove the stub and restore the original function
How to use Let's have some example
- Example #1
const { firestoreGetStub } = require("gcp-testing");
let collections = {}; // IMPORTANT
let getStub = firestoreGetStub(collections);
it("should return rate=0 in case of zero orders", async () => {
// Create documents in Orders collection to be used in the test
collections.Orders = [
{user_id: 0, type: "order", returned: true},
{user_id: 1, type: "order", returned: false},
];
const { orders, rate, returned } = await aggregateReturnRate(2);
// When aggregateReturnRate call collection.where.get
// it won't call the real get
// it will call our fake offline stub (getStub.queryGetStub)
// Assertions
assert.equal(orders, 0);
....
});
after(() => {
// Remove "get" stub, i.e. real get function is back
getStub.restore();
});
More on let collections = {}; // IMPORTANT
Why this is important? this object should be at the test side and passed to the firestoreGetStub
as it acts as our firestore collections, any change done to it will be seen by our get stub.. so, depending on your test case, create or remove documents and collections from it.
- Example #2 What if we need to access the stubs to use its functions provided by sinon api? in this example we're going to access the transaction get stub.
let getStub;
let collections;
before() {
collections = {}
collections.Orders = {
"one":{user_id:"xx", ...},
"two":{user_id:"xx", ...},
"three":{user_id:"xx", ...}
}
getStub = firestoreGetStub(collections);
}
after() {
getStub.restore();
}
it("check testfunction for id '0'", async () => {
await testFunction('0').then(() => {
assert.isTrue(getStub.transactionGetStub.calledOnce, 'Called GET once');
// Find out that the Orders collection is called in a document call
assert.equal(
getStub.transactionGetStub.getCall(0).firstArg._path.segments[0],
"Orders"
);
})
})
As shown in the example, the return of firestoreGetStub
is an Object, that consists of restore
function which restores the original behaviour of all the get stubbed functions, transactionGetStub
which is the stub for transcation get and we can access it and use its functions like calledOnce
for example.
Similar to transactionGetStub
, there are queryGetStub
and docGetStub
.
Firestore Add Stub
This function provides a stub for collection("...").add
How to use Let's have an example
const { firestoreAddStub } = require("gcp-testing");
let addStub = firestoreAddStub(collections);
it("dummy example", () => {
functionUnderTest();
// When functionUnderTest call collection.add
// it won't call the real add, it will call our fake offline stub
// check if add stub is called
assert.equal(
addStub.calledOnce,
true,
"ُExactly One document should be added by this function"
)
// get the return of the add
// (return is s a fake mimic of the real add return)
let doc = await addStub.returnValues[0];
// 0: first call return
...
);
})
after(() => {
// Remove "add" stub
addStub.restore();
});
Firestore Transaction UPDATE Stub
This piece of code mimics a transaction update method. This means all calls
like transaction.update(ref, object)
will be overwritten. This will then use
the collections
object you provided and update the data inside the collections.
It should always be used in combination with the Transaction get Stub
How to use Let's have an example
let collections;
let getStub;
let updateStub;
before() {
collections = {}
collections.Orders = {
"one":{user_id:"xx", keepoalas: 20, ...},
"two":{user_id:"xx", keepoalas: 20, ...},
"three":{user_id:"xx", keepoalas: 20, ...}
}
collections.Aggregation = {
"xx":{
keepoalas:{
keepoalas: 10
}
}
}
getStub = getStub(collections);
updateStub = firestoreTransactionUpdateStub(collections);
}
after() {
getStub.restore();
updateStub.restore();
}
it("check aggregation for single user", async () => {
testFunction('xx').then(() => {
assert.isTrue(currentObject.getStub.transactionGetStub.calledOnce, 'Called GET once');
assert.isTrue(
currentObject.updateStub.calledOnce,
'Called Update once',
);
// should aggregate all Keepoalas from collection "Orders"
assert.equal(
currentObject.collections.Aggregation['xx'].keepoalas.keepoalas,
60,
'keepoalas',
);
});
})
Firestore Transaction SET Stub
Similar to Transaction UPDATE Stub but it will overwrite the document if it exist.
setStub = firestoreTransactionSetStub(this.collections);
Stub for auth users
Not to mess with the authentication library, we created a stub for
admin.auth().getUser(<userid>)
that allows you to add your own users
Users look like this:
{
"uid": "XXXXX",
"email": "[email protected]",
"displayName": "Test User"
}
You can create an object with multiple of these and hand them over, let's have an example:
before() {
this.users = {
0: {
uid: '0',
email: '[email protected]',
displayName: 'TestName',
},
1: {
uid: '1',
email: '[email protected]',
displayName: 'Test1Name',
},
};
this.getUserStub = firebaseAuthStub(this.users);
}
after() {
this.getUserStub.restore()
}
This will simply overwrite all calls like
return admin.auth().getUser(user_id).then((userRecord) => ...);
and return the userRecord
from your collection instead of the real one
Google Storage Buckets Stub
You can find stubbing for google cloud storage buckets in the testing directory (node_modules/gcp-testing/googleCloudStorageStub.js
) , the stub is faking the function call Storage.bucket()
to make it return a fake bucket object that mimics the behavior of the original object but it uses the local file system instead.
How To Use
// Import the stub factory function
const {getBucketStub} = require('gcp-testing');
// Start stubbing
const bucket_stub = getBucketStub();
/**
* Now anything is done using the object returned from Storage.bucket()
* Is being processed on the local filesystem not the real cloud storage
*/
const {Storage} = require('@google-cloud/storage');
const storage = new Storage({
projectId: 'my_project',
});
const bucket = storage.bucket('my_bucket');
// Example 1: write ('my_file.txt') into local bucket ('my_bucket') in my_project
// =================================================================
const tempLocalFile = path.join(os.tmpdir(), 'my_file.txt');
fs.writeFileSync(tempLocalFile, 'this is my file!');
bucket
.upload(tempLocalFile, {
metadata: {
metadata: {
title: 'hello',
},
},
})
.then(
() => console.log('uploaded successfully!'), // uploaded successfully!
);
// Example 2: read ('my_file.txt') file and its metadata from local bucket ('my_bucket') in my_project
// Note: this can be done inside a test to assert file contents
// =================================================================
// I. read file
bucket
.file('my_file.txt')
.download()
.then((file_contents) => {
assert.equal(file_contents.toString(), 'this is my file!');
});
// II. read metadata
bucket
.file('my_file.txt-metadata.json')
.download()
.then((file_contents) => {
const actual_metadata = JSON.parse(file_contents.toString());
const expected_metadata = {metadata: {title: 'hello'}};
assert.deepStrictEqual(actual_metadata, expected_metadata);
});
// =================================================================
// Note that the file is read from the local filesystem:
// 'node_modules/gcp-testing/storage.googleapis.stub/keepoalatesting/my_bucket/my_file.txt'
// Also writing a file using either upload or write stream will write the file in the same path
// =================================================================
// Remove the stub (return back to the original cloud storage)
bucket_stub.restore();
Currently Supported Features
- Bucket.cloudStorageURI
- Bucket.getSignedUrl
- Bucket.upload
- Bucket.file
=> (File Object Stub)
- File.cloudStorageURI
- File.getSignedUrl
- File.createReadStream
- File.createWriteStream
Notes
- The local file sytem is structured similar to that of google cloud storage, such that
the storage is located at the directory
node_modules/gcp-testing/storage.googleapis.stub
, and inside this directory there should be a directory for each bucket. - For each file written to the local storage another file is created store its metadata, which is named after the original file name +
-metadata.json
, for example if a file calledmyfile.txt
is created in bucketmybucket
another file is created inside the bucketmybucket
with the namemyfile.txt-metadata.json
. note that the metadata file is always created even if no metadata is specified (in that case the json file will have an empty object only{}
).
Logs
functions.logger.log
is stubbed to make the logger silent.
the utility module logger.js
is the one required to stub the logger, inside index.test.js
you will realize that it's imported and being used.
By default, all logs in your test will be silent, but you can make use of them.
- Import logger
const {logger} = require('gcp-testing');
- Flush the logger before your test to remove all logs from previous tests
before(()=> {
logger.flush();
})
- Print all the logs at the end of the test
after(() => {
console.log('--- mytest LOGS ---');
logger.print();
});
- Access the logs
logger.logs.forEach(log => ...);
logs
is in this format [{type: string, text: string }]
and
type
can be: warn
, log
, error
,... (i.e. functions.logger.type
)
- To start stubbing and restore the original functions
// start stubbing
logger.stub();
// restore original (i.e. stop stubbing)
logger.restore();
NOTE: in
index.test.js
logger is already imported and bothlogger.stub()
&logger.restore()
are called globally before start and after finish all testing respectively, i.e. you won't need to use these two functions.
NOTE: if you separated your test from
index.test.js
, make sure to pass thelogger
object to your test fromindex.test.js
.