It gives me TypeError: Cannot assign to read only property 'get' of object '#<Config>'.
I'm using mocha 8.0.1, chai 4.2.0, sinon 9.0.2 for Unit Testing.
I'm spying on a method in the config npm package.
Here's how I spy the get method:
...
before(() => {
sandbox = sinon.createSandbox();
configStub = sandbox.spy(config, 'get');
});
after(() => {
sandbox.restore();
});
it('should something', async () => {
console.log('Just logging');
config.get('LOG.LEVEL'); // just to show the point. if I remove this line, it doesn't throw the error
});
...
What happens is, if I run config's get method somewhere during the test, it can not be restored by spy. It throws that read-only property error. But when the config.get function is never called, it doesn't throw that error (I don't understand why not). For stub there's no problem, it can restore just fine.
But the reason I'm using spy is because I want config.get to work like it normally does while I'm testing my module/function that's using it, I just want to spy on it. And I also need to be able to restore it after this test suite. I spy on it because I need to test that it's being called by my module/function with some specific parameters.
How do I spy on a read-only property/method, allow my module/function to use it like it normally does, and then restore it?
Thank you :)
You can set ALLOW_CONFIG_MUTATIONS environment variable to true for the test run.
In this way config.get invocation will not freeze the config which should resolve the issue.
You can find the env var description in the documentation:
https://github.com/lorenwest/node-config/wiki/Environment-Variables#allow_config_mutations
I have a project to add currency details into the firestore database and my project is doing with ionic 3
Whenever I add a new document to the collection a trigger function onCreate() will execute and update the document named 'updated'.
But the trigger function always showing an error.
Error: Invalid use of type "undefined" as a Firestore argument.
at Object.exports.customObjectError.val [as customObjectError] (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/validate.js:164:14)
at Function.encodeValue (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/document.js:808:20)
at Function.encodeFields (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/document.js:678:36)
at Function.fromObject (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/document.js:218:55)
at WriteBatch.set (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/write-batch.js:291:39)
at DocumentReference.set (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/reference.js:419:8)
at Object.<anonymous> (/user_code/lib/index.js:28:10)
at next (native)
at /user_code/lib/index.js:7:71
at __awaiter (/user_code/lib/index.js:3:12)
sombody please help..
i have spent lot of time on it.
Here is the code :
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp();
exports.createCurrency = functions.firestore
.document('Exchange/{ExchangeId}')
.onCreate( async (snap, context) => {
const id: string = snap.data().id;
const branchName: string = snap.data().branchName;
const currencyName: string = snap.data().currencyName;
const buyingRate : string = snap.data().buyingRate;
const sellingRate : string = snap.data().sellingRate;
const newUser= admin.
firestore()
.doc(`Exchange/updated`)
.set({
id : id,
branchName : branchName,
currencyName : currencyName,
sellingRate : sellingRate,
buyingRate :buyingRate
});
return newUser;
});
The error message is this:
Invalid use of type "undefined" as a Firestore argument.
You can see in your stack trace that this happens when you call set() with an object on a DocumentReference. It turns out that one of the values you're passing in the object is undefined. Check each of the values that you're passing and make sure all of them have an actual value:
.set({
id : id,
branchName : branchName,
currencyName : currencyName,
sellingRate : sellingRate,
buyingRate :buyingRate
});
It's impossible to tell which one it is from the error message, so you'll have to print them all out do something to check each and every one of them.
When you .set an object but one of the fields in the object is undefined you will get this error.
The problem is that when you use console.log to display the object it does not show the undefined variables so it is difficult to trace.
Use the following instead of console.log to find the element that is causing the problem.
const util = require('util');
console.log(util.inspect(myObject, {showHidden: false, depth: null}));
This will give you an output as follows:
{ origin: 'AMS',
destination: undefined,
duration: 94,
carrier: 'KL',
flight_number: '2977',
departure: '2019-06-11T15:34:00',
arrival: '2019-06-11T17:08:00',
type: 'flight' }
Understanding the issue:
The following error message is relatively clear
Invalid use of type "undefined" as a Firestore argument.
This means, that in your set or update reference method, you've passed an argument, which had value of undefined, which is by definition not a valid Firestore argument.
Fixing the issue:
Personally I find everything much easier to understand with an example.
Let's say my function editStafferPoolPermissions errors with the "Invalid use of type "undefined" as a Firestore argument" error message. Which is defined the following way:
export const editStafferPoolPermissions(data: {
businessId: string,
stafferId: string,
poolPermissions: string[],
areCustomPermissions: boolean,
wage: number,
}) => //...
To find out which argument (or even arguments) it is exactly
Open your Firebase developer console and open the "Functions" tab.
Select the logs tab
Filter out the exact name of the function and check the arguments passed
This allow us to see, which arguments were passed and which weren't.
As you can see, the wage parameter is missing in the oncall invocation of my https cloud function, causing the error to crash. This means I either forgot to pass or am passing the wage parameter incorrectly.
Obviously the undefined argument will wary depending on how your function is defined but hopefully this should be enough for you to get the gist of how to trace and fix the issue. It usually boils down to two options, you either forgot to pass it altogether or are passing it incorrectly from the front-end (or the data is incorrectly structured)
What if I want to allow undefined (optional) arguments?
What most of the answers on internet don't tackle, is the scenario where we might actually leave an argument undefined on purpose.
I actually had a lot of trouble finding this for the longest time, where I had to resort to writing a very shoddy looking cloud funciton full of nested ifs, when I wanted to create one, that would also allow optional parameters as undefined and simply ignore them if they aren't passed.
To continue from our previous example, let's say we changed the wage argument to optional, i.e.
wage?: number
// ... other params
So now, if we call the editStafferPoolPermissions cloud functions, it shouldn't matter whether the wage is passed or not.
Luckily, as of May 29 2020, there has been added a new argument to the SetOptions called ignoreUndefinedProperties, which allows you to simply ignore undefined parameters.
For example, the inside of my editStafferPoolPermissions could look something like this.
await firestore.collection('staffers').doc(stafferId).set({
poolPermissions,
areCustomPositions,
wage,
}, { ignoreUndefinedProperties: true })
Troubleshooting legacy firebase versions
Given this newly added argument is relatively recent and even in my work I was on relatively older codebase which for legacy reasons could not have the most up-to-date firebase version, so for this reason, I needed to create a polyfill, which would mimic the ignoreUndefinedProperties argument.
I've created the following function:
export const ignoreUndefinedSet = async (
// depending on your namespace
// & /types version, you can use firebase.firestore.<name> instead
reference: FirebaseFirestore.DocumentReference,
data: FirebaseFirestore.DocumentData,
options?: FirebaseFirestore.SetOptions,
checkNestedObjects = true,
) => {
const isPlainObject = (val: unknown) =>
typeof val === 'object' && val !== null &&
!(val instanceof Date) && !Array.isArray(val)
const keepDefinedProperties = (
obj: FirebaseFirestore.DocumentData,
nestedCheck = true,
) =>
Object.entries(data).reduce(
(result, [key, value]) => (
value === undefined
? result
: (nestedCheck && isPlainObject(value))
? Object.assign(result, { [key]: keepDefinedProperties(value) })
: Object.assign(result, { [key]: value })
),
{}
)
const onlyDefinedProperties = keepDefinedProperties(data, checkNestedObjects)
await reference.set(onlyDefinedProperties, { ...options })
}
So with my polyfill, you can use ignore the undefined properties even in older firebase versions. In fact it actually might be useful even on newer ones, because it allows you to decide if you want to ignore the undefined properties only at the object root level, or also at potentially nested object properties.
So essentially these two statements are equivalent
await reference.set(data, { ignoreUndefinedProperties: true })
// newer firebase version
await ignoreUndefinedSet(reference, data) // my polyfill
Note, you can also pass other SetOptions or disable the nested objects check
await ignoreUndefinedSet(reference, data, { merge: true }, false)
// does ignoreUndefinedProperties only at root level and uses the merge method
let nasPath = "";
return getFamInfo(args.familyID)
.then(function (famInfo) {
nasPath = //some code involving famInfo here
return getSFTPConnection(config.nasSettings);
}).then(function (sftp) {
const fastPutProm = Promise.promisify(sftp.fastPut);
return fastPutProm(config.jpgDirectory, nasPath, {});
});
If I put a breakpoint after const fastPutProm = Promise.promisify(sftp.fastPut);, fastPutProm is a function with three arguments. But when I try to run this code, I get a TypeError: Cannot read property 'fastPut' of undefined error. What am I doing wrong here?
That error means that your sftp value is undefined so when you try to pass sftp.fastPut to the promisify() method, it generates an error because you're trying to reference undefined.fastPut which is a TypeError.
So, the solution is to back up a few steps and figure out why sftp doesn't have the desired value in it.
Another possibility is that the error is coming from inside the module and it's because the implementation of sftp.fastPut is referencing this which it expects to be sftp. Your method of promisifying is not preserving the value of this. You can fix that by changing your code to:
const fastPutProm = Promise.promisify(sftp.fastPut, {context: sftp});
For code that throws exceptions, I need to wrap the expectation in an anonymous function. Otherwise the exception is thrown before it can be caught by Mocha.
See this StackOverflow answer.
But wrapping seems to have side effects.
The following code behaves differently when wrapped. it seems to be a Chai problem.
chai = require 'chai'
expect = chai.expect
describe 'Weird', ->
obj =
name: 'kalle'
it 'not wrapped', () ->
expect(obj).to.have.property 'name', 'kalle'
it 'wrapped', () ->
expect(->obj).to.have.property 'name', 'kalle'
My handcrafted Javascript version:
var expect = require('chai').expect;
describe('Weird', function() {
obj = {name: 'kalle'};
it('not wrapped', function() {
expect(obj).to.have.property('name', 'kalle');
});
it('wrapped', function() {
expect(function() {return obj}).to.have.property('name', 'kalle');
});
});
The failure looks like this:
AssertionError: expected [Function] to have a property 'name' of 'kalle', but got ''
Weird
✓ not wrapped
1) wrapped
1 passing (10ms)
1 failing
1) Weird wrapped:
+ expected - actual
+kalle
Why does the wrapping yield different results?
Thanks in advance!
In both cases you are passing the function as a parameter, not the result of the function. I expect that mocha actually calls the function when you call to.throw as is done in the linked SO answer. In order to resolve the function in this situation, you'd want to actually call the function in your expectation:
expect(->obj()).to.have.property 'name', 'kalle'
I'm actually new to JavaScript as well as Jasmine. So it might be something really obvious that fixes my problem but I can't see it.
I want to check if (an already existing) JavaScript application calls console.error() while loading. I don't really see a way how to realise this with Jasmine. I've included the JavaScript file as well as the spec file in the SpecRunner.html.
But I take it that I somehow need to "instantiate" the application in order to test if it throws any errors on the console, right?
Or should I include the SpecRunner.html code only for this purpose into the HTML code of the app?
You can spy on console.error like this:
beforeEach(function(){
spyOn(console, 'error');
})
it('should print error to console', function(){
yourApp.start();
expect(console.error).toHaveBeenCalled();
})
You can override the standard console.error function like this:
//call the error function before it is overriden
console.error( 'foo' );
//override the error function (the immediate call function pattern is used for data hiding)
console.error = (function () {
//save a reference to the original error function.
var originalConsole = console.error;
//this is the function that will be used instead of the error function
function myError () {
alert( 'Error is called. ' );
//the arguments array contains the arguments that was used when console.error() was called
originalConsole.apply( this, arguments );
}
//return the function which will be assigned to console.error
return myError;
})();
//now the alert will be shown in addition to the normal functionality of the error function
console.error( 'bar' );
This solution works with Jasmin or anything else. Just put the code above before the other codes and any call after this to console.error() will call the overridden function.
use toThow and toThrowError http://jasmine.github.io/edge/introduction#section-Spies:_and.throwError