Node.js - start a process (firebase emulators) and read its output - node.js

I want to start firebase emulators before Jest tests.
Execute this, but programatically:
E:\my-projct>firebase emulators:start --only firestore
i emulators: Starting emulators: firestore
i firestore: Serving ALL traffic (including WebChannel) on http://localhost:8080
! firestore: Support for WebChannel on a separate port (8081) is DEPRECATED and will go away soon. Please use port above instead.
i firestore: Emulator logging to firestore-debug.log
+ firestore: Emulator started at http://localhost:8080
i firestore: For testing set FIRESTORE_EMULATOR_HOST=localhost:8080
+ All emulators started, it is now safe to connect.
Therefore, I need to:
Execute the command
Wait for the "All emulators started" string to appear.
How do I read the output?
I have tried the following, all it prints is a newline.
const cp = require('child_process');
const child = cp.exec('firebase emulators:start --only firestore');
child.stdout.addListener('data', data => console.log(data.toString()));

This is exactly why we have made the firebase emulators:exec command.
So let's say you use "npm run test" to run your Jest scripts, you would use:
firebase emulators:exec "npm run test"
This will:
Start the emulators and wait till they're ready
Run your script (npm run test in this case)
Wait for your script to exit
Cleanly shut down the emulators

Related

why process.env.FIRESTORE_EMULATOR_HOST will be undefined in test environment? even though I have run the emulator

I am using
Node 14
firebase functions-test: 0.2.3
firebase-admin: 9.6.0
firebase-functions: 3.13.2
firebase tools: 9.10.0
mocha: 8.3.2,
ts-node: 9.1.1
if I run firebase emulators:start
then I will expect process.env will have properties like
"FUNCTIONS_EMULATOR": "true",
"FIRESTORE_EMULATOR_HOST": "localhost:8080"
now I need to create testing for my cloud function using mocha, I will test it using Firestore and functions emulator, I give my file name events_cron_job.test.ts
after I run the simulator, I try to console log those values and I got undefined like this
I expect
console.log(process.env.FUNCTIONS_EMULATOR) // will be "true"
console.log(process.env.FIRESTORE_EMULATOR_HOST) // will be "localhost:8080"
I think this is only happen in test environment ( I am using mocha ), I have regular http triggers and it works as expected
before running the emulator, I try to execute this on terminal
export FIRESTORE_EMULATOR_HOST="localhost:8080"
export FUNCTIONS_EMULATOR="true"
and then execute firebase emulators:start , but the result will be the same, I still get undefined for those values
because those values are undefined, then if my laptop is disconnected from the internet, then this testing will not run ( i.e it will only access the data in production server, not the emulator! ), I expect the test will still run even if there is no internet connection since I use emulator.
I run the mocha test using
mocha -r ts-node/register src/tests/cloud_function_tests --recursive --extension .test.ts --timeout 60000 --exit
As you can see in this Github issue comment, in order to run tests with the Firebase emulator you should use the following command to trigger it:
firebase emulators:exec "npm test"
As this is how tests where intended to be executed by the Firebase Team using the emulator.

why I am still getting firestore data from production when performing testing using emulator?

I am using
Node 14
firebase functions-test: 0.2.3
firebase-admin: 9.6.0
firebase-functions: 3.13.2
firebase tools: 9.10.0
I am trying to perform unit testing of my scheduled cloud function. my cloud functions is like this
export const deleteExpiredEvents = functions
.pubsub.schedule("0 0 * * *")
.timeZone("Asia/Jakarta")
.onRun(async (context) => {
// delete expired events
});
I test the function above like this
import * as admin from "firebase-admin";
import * as firebase from "firebase-functions-test";
const test = firebase({
databaseURL: "https://xxxx.firebaseio.com",
projectId: projectID,
}, "./service-account.json");
const db = admin.initializeApp().firestore();
beforeEach( async () => {
// create dummy expired event
await db.doc(`events/${event1.eventID}`).set(event1);
await db.doc(`events/${event2.eventID}`).set(event2);
await db.doc(`events/${event3.eventID}`).set(event3);
});
it("should not delete events that not expired", async () => {
const wrap = test.wrap(myFunctions.deleteExpiredEvents);
const context = {};
wrap(context); // execute function
const querySnapshot = await db.collection("events").get();
querySnapshot.docs.forEach((snapshot) => console.log(snapshot.data().eventID));
// the rest of my code test ....
});
as you can see from the code above, in before block, I try to create dummy event data, so I expect my firestore emulator will be populated with these 3 dummy event data.
and then in the it block, I try to get all events and then console.log all the eventIDs
I expect I will get 3 events from firestore emulator that previously created in the before block, but I don't know why I get 74 events (instead of just 3), and it comes from firestore production data, not from firestore emulator
I believe I already run firebase emulators:start , firestore, functions and pubsub emulators was running when I executed the test.
so why I am still getting firestore data from production when emulators has been started?
I try to read the documentation from here: https://firebase.google.com/docs/emulator-suite/connect_firestore
it is said:
The Firebase Admin SDKs automatically connect to the Cloud Firestore
emulator when the FIRESTORE_EMULATOR_HOST environment variable is set:
export FIRESTORE_EMULATOR_HOST="localhost:8080"
so I run
export FIRESTORE_EMULATOR_HOST="localhost:8080"
and then
firebase emulators:start
it still get the data from production. in fact, if I disconnect from the internet, the test will not run!
I don't know why if check
console.log(process.env.FIRESTORE_EMULATOR_HOST); // I expect, it will be will be "localhost:8080"
console.log(process.env.FUNCTIONS_EMULATOR); // I expect, it will be "true"
but I will get undefined for both values, like this
after reading github issue in here. I finally find the solution!
first, in your package.json , write your test script. my script is like this
"scripts": {
"test-security-rules": "mocha -r ts-node/register src/tests/firestore_security_rules_tests --recursive --extension .test.ts --timeout 60000 --exit",
"test-cloud-functions": "mocha -r ts-node/register src/tests/cloud_function_tests --recursive --extension .test.ts --timeout 60000 --exit"
}
turn off your emulators, and don't run firebase emulators:start
. but execute
firebase emulators:exec "npm run test-cloud-functions"
and then if you print
console.log(process.env.FIRESTORE_EMULATOR_HOST);
that value will no longer undefined, but it will be "localhost:8080" in my case
here is the documentation about exec
after updating to the latest firestore emulator (v1.11.14.jar)
I think I find this solution
run your simulator as usual using firebase emulators:start
in your terminal, run
export FIRESTORE_EMULATOR_HOST="localhost:8080" (for mac)
set FIRESTORE_EMULATOR_HOST="localhost:8080" (for windows)
run your test as usual.

Prevent firebase emulator closing on finishing the tests

I am running my firebase tests using the following command
firebase emulators:exec --ui 'mocha --reporter spec --timeout 10000'
but the ui closes and emulators shut down as soon as the test finishes. I have looked at the params in the help but cant seem to find a way to keep them running so I can check values in the db.
I have tried starting the emulators first and then running with exec but it always complains that emulator instances are already running.
Is there a method of doing this?
You can try starting the firebase emulators:start and then running your test script after it has started
mocha --reporter spec --timeout 10000
It fails when you run exec after starting because the ports assigned to the emulators are busy.. Instead of running exec after the emulators have started, run the test script itself

Firebase cloud function local code changes are not reflected in emulators

I am trying to develop API for my apps using Firebase cloud functions.
Following this site to use the firebase emulator suite for development and testing locally.
Issue: The changes are not reflected in the locally emulated functions.
Steps:
index.js:
exports.test = functions.https.onRequest(async (request, response) => {
response.status(200).send("First");
});
Successfully deployed the test method.
firebase deploy --only functions:test
In Postman made the following GET request.
https://us-central1-<project-name>.cloudfunctions.net/test
Result: First
Status: 200 OK
Started the emulators:
firebase emulators:start --only functions
In Postman made the following GET request.
http://localhost:5001/<project-name>/us-central1/indexTest
Result: First
Status: 200 OK
Same as the actual deployed function.
Changed the function code to:
exports.test = functions.https.onRequest(async (request, response) => {
response.status(200).send("Second");
});
Getting the same result as before when hitting the emulated function in localhost. The changes are not reflected.
Also, tried stopping the emulator and starting it again. No luck.
I had raised an issue in the firebase-tools repo as suggested by DougStevenson.
Got the issue resolved with Sam Stern's support.
Posting the solution here for anyone else who gets stuck in the same issue.
Solution:
After every change to the ts files, run "npm run build" to compile the code again.
Change "build": "tsc" to "build": "tsc -w" in package.json if you want to auto-compile after every change.

Problem running JS file with firebase emulators: exec

I was trying out the new Firebase Emulators UI announced on May 21, 2020. In the Firebase Docs, I noticed that there is a CLI command that allows us to run a script file:
firebase emulators:exec scriptpath
Run the script at scriptpath after starting emulators for the
Firebase products configured in firebase.json. Emulator processes will
automatically stop when the script has finished running.
Although the docs does not mention what kind of script file it should be, I presume it could be either a test script or a workable JavaScript file that be executed standalone with node filename.js without any errors. This makes sense because I can actually run an initial JS file that pre-populate some test data to the Firestore Emulator for further testing.
But since I've previously started the emulators, I have to open a new instance of command window to run the firebase emulators:exec command. Unfortunately, I hit the following errors:
D:\Firebase\my-project>firebase emulators:exec setup-db.js
i emulators: Starting emulators: functions, firestore, database, hosting, pubsub
! hub: emulator hub unable to start on port 4400, starting on 4401 instead.
! emulators: It seems that you are running multiple instances of the emulator suite for project my-project-id. This may result in unexpected behavior.
i emulators: Shutting down emulators.
i hub: Stopping emulator hub
! functions: Port 5001 is not open on localhost, could not start Functions Emulator.
! functions: To select a different host/port, specify that host/port in a firebase.json config file:
{
// ...
"emulators": {
"functions": {
"host": "HOST",
"port": "PORT"
}
}
}
i emulators: Shutting down emulators.
Error: Could not start Functions Emulator, port taken.
Take note of this line:
! emulators: It seems that you are running multiple instances of the emulator suite for project my-project-id. This may result in unexpected behavior.
So, how can I run the firebase emulators:exec AFTER the emulators start as mentioned in the Firebase Docs and, can I run the JS file for the said purpose? Thanks in advance!
UPDATE:
If I run just the firebase emulators:exec setup-imtp-db.js without prior running the firebase emulators:start command, I ran into the following errors:
Note that the setup-imtp-db.js does not contain any Cloud Functions code but just a standalone JS file that populates data from another JSON file into Firestore via Admin SDK. I'm using Node.js 12.14.0 and firebase-tools 8.4.0.
According to Firebase CLI Github README.md:
emulators:exec Start the local Firebase emulators, run a test
script, then shut down the emulators.
So the scriptpath parameter isn't really meant to run a JavaScript file that pre-populates data on Firestore Emulator (and presents on its Emulator UI) for the purpose of UI Functional Test or Integrated Test. The emulators:exec will immediately shut down the emulators after finish executing the test script.
As #Doug Stevenson suggested in the comment, if a proper error handling is expected from the emulators or a new feature is required, a request or bug report can be posted on Github for firebase-tools.
While the emulators:exec command is clearly not meant for seeding a database while continuing (manual) testing, as explained in the accepted answer, I did accomplish something of the kind.
I successfully prevented my emulators:exec-invoked database seeding script from exiting with the following method:
// db-seeder.js
const admin = require('firebase-admin');
const app = admin.initializeApp({
projectId: 'demo-test'
});
const db = admin.firestore(app);
...
// Prevents the seed Node.js process from exiting,
// which in turn prevents the emulators from exiting.
process.stdin.resume();
Along with the --ui switch, this gives an experience comparable to running emulators:start, but then with a seeded database:
firebase --project demo-test emulators:exec --ui db-seeder.js
I had to use this method because I was getting credential problems when trying to seed the database with a standalone script, see details here on dev.to.

Resources