Firebase Firestore SDK will not run with Mocha+Chai unit tests - node.js

Issue:
When running a test in Mocha, we need to add a document to the local emulated Firestore. The local emulated setup is working but not within the Mocha test, specifically.
Node Version: 14.18.1, chai: 4.3.4, firebase-admin: 10.0.0, firebase: 9.1.3
Explaination
Working Locally:
If I were to try and run:
await conref.doc('testDoc').set({test: true}); before the first Mocha
describe(), I can verify the document is added to my local Firestore at
localhost:8080
const db = getFirestore();
const ref = db.collection('testCollection');
await ref.doc('testDoc').set({test: true});
const testDoc = await db.collection('testCollection').doc('testDoc').get();
console.log(testDoc.data());
prints { test: true }, confirming packages and connection is working
Breaking In Mocha:
..however placing similar logic inside my describe() causes the test to report as
errored each and every time, returning a red (1) instead of a green checkmark:
describe("example", async function() {
it("should write to Firestore in a mocha test", async function() {
const db = getFirestore();
const ref = db.collection('testCollection');
await ref.doc('testDoc').set({test: true}); // < causes error
};
};
screenshot
Imports
Firebase has had different version and its been confusing, so I will include the imports I am using for both of these examples below, in case this might be very wrong.
import { getFirestore } from 'firebase-admin/firestore';
import { initializeApp, applicationDefault } from 'firebase-admin/app';
initializeApp({
credential: applicationDefault(),
databaseURL: process.env.FIREBASE_DATABASAE_URL,
});
Attempts
Tried adjusting timeouts, thinking maybe the Mocha suite was not waiting for the doc to be added, but even an addition of this.timeout(10000); within the test did not change the outcome.
Tried setting the doc before the test, which worked, so I put this in a function and tried calling it from within the test as if I was praying on a Hail Mary, but to no surprise this had no effect.
Tried connecting to my production Firestore but still no cigar (via removing the FIRESTORE_EMULATOR_HOST env).
Added done() at the end of the test, but did not change outcome

Related

run onPrepare function synchronously in Jasmine, before executing specs

Hello stackoverflow community,
The task
I'm running Jasmine tests programmatically using jasmine.execute(). My task is to run onPrepare function, where I do some setup work like setting up reporters etc, and I need that function to be synchronous (has to be finished before running specs)
Approach #1
The first approach I tried was to just declare an async onPrepare function, which also includes the code for specifying reporters, and then do
await onPrepare();
await jasmine.execute();
Problem
In the result I get jasmine.getEnv() is not a function. I assume because getEnv() becomes available as .execute() is ran. So I understand this won't work
Approach #2
The next thing I tried was to create a helper file with my sync code, specify it in the config and run jasmine.execute();.
So, if simplified, I have
// conf.js
(async () => {
let Jasmine = require('jasmine');
let jasmine = new Jasmine();
let variables = require("./variables.json");
let {spawn} = require("child_process");
let childProcess = spawn(variables.webdriver);
console.log(`Webdriver started, pid: ${childProcess.pid}`);
jasmine.exitOnCompletion = false;
jasmine.loadConfig({
'spec_files': ['specs/*.spec.js'],
'helpers': ['on-jasmine-prepare.js'],
'stopSpecOnExpectationFailure': false,
'random': false,
})
const result = await jasmine.execute();
console.log('Test status:', result.overallStatus);
console.log('Closing library and webdriver process');
await library.close();
await childProcess.kill();
console.log('webdriver killed:', childProcess.killed);
})()
// on-jasmine-prepare.js
(async () => {
const {SpecReporter} = require("jasmine-spec-reporter");
const library = require("./library/library");
const variables = require("./variables.json");
const errorHandler = require("./modules/on-error-handler");
jasmine.getEnv().clearReporters();
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3 * 60 * 1000;
jasmine.getEnv().addReporter(new SpecReporter({}))
global.library = new library.Library(variables.IP);
console.log('library instantiated')
await library.deleteSessions();
console.log('sessions deleted')
await library.launch(library.home);
console.log('home page launched')
jasmine.getEnv().addReporter(
errorHandler(library)
)
console.log('debugger reporter added' )
})();
Problem
The problem that I noticed that the helper file is executed asynchronously with the specs and I get a spec error before the helper function finishes (basically because of race condition). Below the output example, where you can see some console.log from onPrepare was ran after spec started
Webdriver started, pid: 40745
library instantiated
Jasmine started
sessions deleted
Example
✗ App is loaded (7 secs)
- Error: Session already exist
home page launched
debugger reporter added
The question
How do I run onPrepare function synchronously, before specs? Preferably natively (using only jasmine capabilities). Otherwise maybe using third party packages. I know it's possible because #protractor had this feature, however I couldn't back-engineer it
MacOS
node v16.13.2
jasmine v4.1.0
Thank you

Jest initialize and shared objects once per test suite and across test cases

I want to use shared resources between jest test suites. I read in the internet and found that this could be the solution. But the setup is invoked per each test file.
I have two test files links.test.js and 'subscritpions.test.js'. I usually call them with one command jest and that all.
The problem is that the setup function of my custom environment custom-environment.js:
const NodeEnvironment = require('jest-environment-node');
const MySql = require('../../lib/databases/myslq/db');
class CustomEnvironment extends NodeEnvironment {
constructor(config) {
super(config)
}
async setup() {
await super.setup();
console.log(`Global Setup !!!!!!!!!`);
this.global.gObject = "I am global object"
this.global.liveUsers = await new MySql("Live Users");
this.global.stageUsers = await new MySql("Stage Users");
}
async teardown() {
console.log(`Global terdown !!!!!!!!!`);
await super.teardown();
this.global.gObject = "I am destroyed";
this.global.liveUsers.closeConnection();
this.global.stageUsers.closeConnection();
}
runScript(script) {
return super.runScript(script)
}
}
module.exports = CustomEnvironment;
is called twice for each test:
Global Setup !!!!!!!!!
Global Setup !!!!!!!!!
ERROR>>> Error: listen EADDRINUSE: address already in use 127.0.0.1:3306
So it tries to establish second connection to the same port - while I could simply use the existing connection.
The way it works seems to me makes no difference from defining
beforeAll(async () => {
});
afterAll(() => {
});
hooks.
So to wrap up, the question is: Using jest command (thus running all test suits), how can I invoke setup function once for all test and share global objects across them?
setup and teardown are indeed executed for each test suite, similarly to top-level beforeAll and afterAll.
Test suites run in separate processes. Test environment is initialized for each test suite, e.g. jsdom environment provides fake DOM instance for each suite and cannot be cross-contaminated between them.
As the documentation states,
Note: TestEnvironment is sandboxed. Each test suite will trigger setup/teardown in their own TestEnvironment.
The environment isn't suitable for global setup and teardown. globalSetup and globalTeardown should be used for that. They are appropriate for setting up and shutting down server instances, this is what documentation example shows:
// setup.js
module.exports = async () => {
// ...
// Set reference to mongod in order to close the server during teardown.
global.__MONGOD__ = mongod;
};
// teardown.js
module.exports = async function () {
await global.__MONGOD__.stop();
};
Since this happens in parent process, __MONGOD__ is unavailable in test suites.

Mocha (Spectron) suddenly times out on async test scenarios

I wanted to run some Spectron e2e tests I wrote some weeks ago, but to my surprise, suddenly they all stopped working for one and the same reason.
According to the error message, I'm dealing with rejected Promises, but I can't figure out where the problem is coming from. Calling done at the end of my testcase raises the exact same error.
I'm running the following command to launch my test suit: mocha test/e2e
Mocha then executes this index.js before running my tests in ordner to support ES6+ features
'use strict'
//index.js
// Set BABEL_ENV to use proper env config
process.env.BABEL_ENV = 'test'
// Enable use of ES6+ on required files
require('babel-register')({
ignore: /node_modules/
})
// Attach Chai APIs to global scope
const { expect, should, assert } = require('chai')
global.expect = expect
global.should = should
global.assert = assert
// Require all JS files in `./specs` for Mocha to consume
require('require-dir')('./specs')
After that its trying to run this small Login.spec.js which returns the error mentioned above
import utils from '../utils'
import {Application} from "spectron";
import electron from "electron";
describe('🔑 Login', function () {
this.timeout(11000);
it('login form exists', async function (done) {
this.app = new Application({
path: electron,
env: {"SPECTRON_RUNNING":true},
args: ['dist/electron/main.js'],
startTimeout: 10000,
waitTimeout: 10000
})
await this.app.start()
await this.app.client.windowByIndex(1);
done();
})
})

Timeout when attempting to connect to mongo from jest unit tests

I want to write some unit tests with jest and mongoose to validate data interaction with mongo.
I don't want to mock mongoose here because I specifically want to validate the way that mongo documents are created/modified/handled.
package.json is configured to leave node modules unmocked:
{
"jest": {
"unmockedModulePathPatterns": [
"node_modules"
]
}
}
In my actual test, I have set up a beforeAll() hook to take care of connecting to mongo:
const mongoose = require('mongoose');
describe('MyTest', () => {
beforeAll((done) => {
mongoose.connect('mongodb://127.0.0.1:27017/test');
let db = mongoose.connection;
db.on('error', (err) => {
done.fail(err);
});
db.once('open', () => {
done();
});
});
it('has some property', () => {
// should pass but actually never gets run
expect(1).toBe(1);
});
});
Here's the output:
/usr/local/bin/node node_modules/jest-cli/bin/jest.js --verbose
Using Jest CLI v0.10.0, jasmine2
FAIL src/lib/controllers/my-controller/__tests__/my-test.js (5.403s)
MyTest
✕ it has some property
MyTest › it has some property
- Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
at Timer.listOnTimeout (timers.js:92:15)
1 test failed, 0 tests passed (1 total in 1 test suite, run time 5.903s)
Process finished with exit code 1
The test times out every time because done() is never called in the beforeAll() hook - no errors thrown nor anything printed to console. I can place breakpoints in the beforeAll() hook to validate the code is being run. It appears that mongoose is hanging whilst attempting to open a connection to Mongo, and then the jest tests are timing out.
When I'm running similar code outside of the jest environment, it connects as expected (nigh on instantly). Suspecting that it could be causing problems, I've experimented with disabling jest's automock feature completely, but the behaviour remains unchanged.
I imagine I've missed something incredibly obvious... any ideas what could be going on?
jest-cli v. 0.10.0
mongoose v. 4.4.11
Updates:
Tried replacing ES6 arrow function syntax with plain function(done) {}. No difference.
Tried making the test async by passing done parameter, and calling it on test completion. No difference.
Tried calling mongoose.connect() after the declaration of error and connected event handlers
Tried commenting out all mongoose-related code to check that the beforeAll() hook is working correctly - it is.
Jest-jasmine is different from Jasmine.
The syntax you used actually belongs to Jasmine 2.0+, not Jest-jasmine. So if you want to continue to use jest, you have to look into jest docs to find the answer.
Even more, "beforeAll" is not a standard jest injected variables, see jest api.
I got your code run with Jasmine 2.3.4, it runs just fine. I tried Promise/pit in jest to finish this job but failed.
First, install jasmine.
npm install -g jasmine
mkdir jasmine-test
cd jasmine-test
jasmine init
jasmine examples
touch spec/mongodbspec.js
Here is my directory structure:
fenqideMacBook-Pro:jasmine-test fenqi$ ls -R
.:
spec/
./spec:
mongodbspec.js support/
./spec/support:
jasmine.json
Then, edit spec/mongodbspec.js, I just add one line " 'use strict'; " to your code.
'use strict';
const mongoose = require('mongoose');
describe('MyTest', () => {
beforeAll((done) => {
mongoose.connect('mongodb://127.0.0.1:27017/test');
let db = mongoose.connection;
db.on('error', (err) => {
done.fail(err);
});
db.once('open', () => {
done();
});
});
it('has some property', () => {
// should pass but actually never gets run
expect(1).toBe(1);
});
});
Last, run 'jasmine' :
fenqideMacBook-Pro:jasmine-test fenqi$ jasmine
Started
.
1 spec, 0 failures
Finished in 0.023 seconds

Mocha and ZombieJS

I'm starting a nodejs project and would like to do BDD with Mocha and Zombiejs. Unfortunately I'm new to just about every buzzword in that sentence. I can get Mocha and Zombiejs running tests fine, but I can't seem to integrate the two - is it possible to use Mocha to run Zombiejs tests, and if so, how would that look?
Just looking for "hello world" to get me started, but a tutorial/example would be even better.
Thanks!
Assuming you already have installed mocha, zombie and expect.js according to instructions, this should work for you:
// Put below in a file in your *test* folder, ie: test/sampletest.js:
var expect = require('expect.js'),
Browser = require('zombie'),
browser = new Browser();
describe('Loads pages', function(){
it('Google.com', function(done){
browser.visit("http://www.google.com", function () {
expect(browser.text("title")).to.equal('Google');
done();
});
});
});
Then you should be able to run the mocha command from your root application folder:
# mocha -R spec
Loads pages
✓ Google.com (873ms)
✔ 1 tests complete (876ms)
Note: If your tests keep failing due to timeouts, it helps to increase mocha's timeout setting a bit by using the -t argument. Check out mocha's documentation for complete details.
I wrote a lengthy reply to this question explaining important gotchas about asynchronous tests, good practices ('before()', 'after()', TDD, ...), and illustrated by a real world example.
http://redotheweb.com/2013/01/15/functional-testing-for-nodejs-using-mocha-and-zombie-js.html
if you want to use cucumber-js for your acceptance tests and mocha for your "unit" tests for a page, you can use cuked-zombie (sorry for the advertising).
Install it like described in the readme on github, but place your world config in a file called world-config.js
`/* globals __dirname */
var os = require('os');
var path = require('path');
module.exports = {
cli: null,
domain: 'addorange-macbook': 'my-testing-domain.com',
debug: false
};
Then use mocha with zombie in your unit tests like this:
var chai = require('chai'), expect = chai.expect;
var cukedZombie = require('cuked-zombie');
describe('Apopintments', function() {
describe('ArrangeFormModel', function() {
before(function(done) { // execute once
var that = this;
cukedZombie.infectWorld(this, require('../world-config'));
this.world = new this.World(done);
// this inherits the whole world api to your test
_.merge(this, this.world);
});
describe("display", function() {
before(function(done) { // executed once before all tests are run in the discribe display block
var test = this;
this.browser.authenticate().basic('maxmustermann', 'Ux394Ki');
this.visitPage('/someurl', function() {
test.helper = function() {
};
done();
});
});
it("something on the /someurl page is returned", function() {
expect(this.browser.html()).not.to.be.empty;
});

Resources