Can sinon stub withArgs match some but not all arguments - sinon

I have a function I am stubbing that gets called with multiple arguments. I want to check just the first argument. The rest are callback function, so I want to leave them alone. Thus, I might have the following 2 calls, using ajax as an example:
method.get = sinon.stub();
method.get(25,function(){/* success callback */},function(){/* error callback */});
method.get(10,function(){/* success callback */},function(){/* error callback */});
I cannot use method.get.calls... because then it doesn't differentiate between the first one get(25) and the second get(10). But if I use method.get.withArgs(25).calls... then it does not match either, because withArgs() matches all arguments, which this does not (and never could, with callbacks like that).
How do I get sinon stubs to check and set responses based on just the 1st arg?

https://sinonjs.org/releases/latest/matchers/#sinonmatchany
You can use sinon.match.any:
method.get.withArgs(25, sinon.match.any, sinon.match.any);

Solution
withArgs can be used to match some but not all the arguments.
Specifically, method.get.withArgs(25) will check just the first argument.
Correction
This is incorrect:
withArgs() matches all arguments
Details
When withArgs is called it remembers the arguments it was passed here as matchingArguments.
Then when the stub is called it gets all matching fakes here.
matchingFakes is called without a second parameter so it returns all fakes that have matchingArguments that match the arguments passed to the stub starting at index 0 up to the length of matchingArguments. This means that a fake will match when its matchingArguments match the beginning of the arguments passed even if there are additional arguments.
Any matching fakes are then sorted by matchingArguments.length and the one that matches the most arguments is the one that is invoked.
The following test confirms this behavior and passes with sinon version 1.1.0 from 7 years ago, version 1.14.0 from the time this question was asked, and the current version 6.3.5:
import * as sinon from 'sinon';
test('withArgs', () => {
const stub = sinon.stub();
stub.withArgs(25).returns('first arg is 25!');
stub.returns('default response');
expect(stub(25)).toBe('first arg is 25!'); // SUCCESS
expect(stub(25, function () { }, function () { })).toBe('first arg is 25!'); // SUCCESS
expect(stub(10, function () { }, function () { })).toBe('default response'); // SUCCESS
});

If you just want to check the first argument you can use
method.get.withArgs(25).calledOnce
or
method.get.calledWith(25)

this method works very well with spies if you want to check only one argument among many
it('should check only first argument', function ():void {
myFunction('foo', 'bar', baz');
expect(myFunctionSpy.firstCall.args[0]).to.equal('foo');
});
However I don't understand why you are using stubs here. If you just want to check how the function is called you should use a spy. If you want to check how it's called AND change it's behaviour (ex: blocking ajax calls) then you should use a mock.
Sinon mocks have their own way of checking stuff. The only way I know for your case would be to use sinon.match.many for the arguments you don't want to check:
it('should check only first argument', async function (): Promise<void> {
mock.expects('myFunction').withExactArgs('foo', sinon.match.any, sinon.match.any).returns('foo');
await myFunction('foo', 'bar', baz');
mock.verify();
});
mock.verify() will proceed to the test AND reset the mock for other tests, in case of using a spy or a stub you should do it mannually with restore() or reset() after each test
PD: sorry about TypeScript syntax here :p

Related

A function to read data from FireBase but requires Unit instead

I've made a function that calls on the FireBase database and will return a MutableList. However, when I try to make it return on a specific line, it says it requires a Unit instead of the MutableList.
fun firebaseCollect(key: String): MutableList<CustomList> {
var ref = FirebaseDatabase.getInstance().getReference(key)
var lessonList = mutableListOf<CustomList>()
ref.addValueEventListener(object: ValueEventListener{
override fun onCancelled(p0: DatabaseError?) {
}
override fun onDataChange(p0: DataSnapshot?) {
if (p0!!.exists()) {
lessonList.clear()
for (index in p0.children) {
val lesson = index.getValue(CustomList::class.java)
lessonList.add(lesson!!)
}
return lessonList
}
}
})
return lessonList
}
Type mismatch. Required: Unit, Found: MutableList< CustomList > is found at the first return lessonList since what I am asking for it to return is a MutableList not a Unit. I am confused as to why this happens. The last return would give an empty list. It is currently my first jab at FireBase and this is a practice I am doing. The rules for read and write have been set to public as well. How should I recode the function that I am able to return the data from FireBase into the function and passed back to the caller?
Firebase APIs are asynchronous. For your case, that means addValueEventListener returns immediately. Then, some time later, the listener you passed to it will be invoked with the data you're looking for. Your return statement in the callback doesn't actually return any data to the caller. In fact, you can't return anything from that callback. At the bottom of your function, when you return lessonList, you're actually returning an initially empty list to the caller, which may change later when the data finally arrives.
To get a better sense of how your code works, put log lines in various places, and see for yourself the order in which the code is invoked. You can read more about why Firebase APIs are asynchronous by reading this article. The bottom line is that you'll need to interact with the asynchronous APIs using asynchronous programming techniques. Don't try to make them synchronous.
Data is loaded asynchronously from Firebase. Once the data is fetched the method onDatachange() is invoked.
You are returning lessonList inside onDatachange(). Return type of onDatachange() is void(Unit in kotlin). This is the reason for the type mismatch error.
For returning the result from the method onDatachange() try this.

Returned value from Meteor Helper not showing up in template

I have a Meteor Helper that does a GET request and am supposed to get response back and pass it back to the Template, but its now showing up the front end. When I log it to console, it shows the value corerctly, for the life of mine I can't get this to output to the actual template.
Here is my helper:
UI.registerHelper('getDistance', function(formatted_address) {
HTTP.call( 'GET', 'https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=Washington,DC&destinations='+formatted_address+'&key=MYKEY', {}, function( error, response ) {
if ( error ) {
console.log( error );
} else {
var distanceMiles = response.data.rows[0].elements[0].distance.text;
console.log(response.data.rows[0].elements[0].distance.text);
return distanceMiles;
}
});
});
In my template I pass have the following:
{{getDistance formatted_address}}
Again, this works fine and shows exactly what I need in the console, but not in the template.
Any ideas what I'm doing wrong?
I posted an article on TMC recently that you may find useful for such a pattern. In that article the problem involves executing an expensive function for each item in a list. As others have pointed out, doing asynchronous calls in a helper is not good practice.
In your case, make a local collection called Distances. If you wish, you can use your document _id to align it with your collection.
const Distances = new Mongo.collection(); // only declare this on the client
Then setup a function that either lazily computes the distance or returns it immediately if it's already been computed:
function lazyDistance(formatted_address){
let doc = Distances.findOne({ formatted_address: formatted_address });
if ( doc ){
return doc.distanceMiles;
} else {
let url = 'https://maps.googleapis.com/maps/api/distancematrix/json';
url += '?units=imperial&origins=Washington,DC&key=MYKEY&destinations=';
url += formatted_address;
HTTP.call('GET',url,{},(error,response )=>{
if ( error ) {
console.log( error );
} else {
Distances.insert({
formatted_address: formatted_address,
distanceMiles: response.data.rows[0].elements[0].distance.text
});
}
});
}
});
Now you can have a helper that just returns a cached value from that local collection:
UI.registerHelper('getDistance',formatted_address=>{
return lazyDistance(formatted_address);
});
You could also do this based on an _id instead of an address string of course. There's a tacit assumption above that formatted_address is unique.
It's Meteor's reactivity that really makes this work. The first time the helper is called the distance will be null but as it gets computed asynchronously the helper will automagically update the value.
best practice is not to do an async call in a helper. think of the #each and the helper as a way for the view to simply show the results of a prior calculation, not to get started on doing the calculation. remember that a helper might be called multiple times for a single item.
instead, in the onCreated() of your template, start the work of getting the data you need and doing your calculations. store those results in a reactive var, or reactive array. then your helper should do nothing more than look up the previously calculated results. further, should that helper be called more times than you expect, you don't have to worry about all those additional async calls being made.
The result does not show up because HTTP.call is an async function.
Use a reactiveVar in your case.
Depending on how is the formated_address param updated you can trigger the getDistance with a tracker autorun.
Regs
Yann

How can you add a hook to the beginning of all Meteor method calls?

For instance, I'd like to throw an error whenever any method is called by clients who are not logged in, and I want to rate limit methods called by logged-in clients. I'm hoping there's a better solution than
Meteor.methods
foo: ->
checkLoggedInAndRate()
...
bar: ->
checkLoggedInAndRate()
...
...
You can use some of these JavaScript tricks :
First, let's define a new Meteor.guardedMethods function that we'll use to define our guarded methods, it will take as arguments the regular methods object as well as the guard function we want to use :
Meteor.guardedMethods=function(methods,guard){
var methodsNames=_.keys(methods);
_.each(methodsNames,function(methodName){
var method={};
method[methodName]=function(){
guard(methodName);
return methods[methodName].apply(this,arguments);
};
Meteor.methods(method);
});
};
This function simply iterates over the methods object and redefine the underlying function by first calling our guard function and then the true method.
Here is a quick example of using our newly defined method, let's define a dummy guard function and a Meteor method to test it with :
function guard(methodName){
console.log("calling",methodName);
}
Meteor.guardedMethods({
testMethod:function(){
console.log("inside test method");
}
},guard);
The sample output of this method will be :
> calling testMethod
> inside test method

How to GetLocalizedItems using Tridion Anguilla Framework?

I would like to use the GetLocalizedItems method in the Anguilla Framework.
I don't know how to create a new filter and set the conditions or what to use for success and failure.
In the GUI:
tridion.Web.UI.ContentManager.WhereUsed.GetListUsedItems(id, filter.conditions,
filter.columns, success, failure);
Are the methods in this namespace intended to be used by our extensions?
Building a filter
Here is an example of how to build a filter
var filter = new Tridion.ContentManager.ListFilter();
filter.conditions.ItemTypes = 16 | 2; // folders and components
filter.conditions.Recursive = true;
filter.conditions.BasedOnSchema = "tcm:1-23-8,tcm:1-32-8".split(",");
filter.columns = Tridion.Constants.ColumnFilter.DEFAULT;
Or this extremely simple case from General.js:
var templateFilter = new Tridion.ContentManager.ListFilter({
conditions: { ItemTypes: [ itemType ] }
});
Calling a WCF method
The second part of your question was really already covered in https://stackoverflow.com/a/9385975/209103, although I'll make it a bit more concrete here.
WCF/AJAX calls such as this are executed asynchronously, since they may take some time to complete. While you would normally simple handle the result of the call on the line after the closing parenthesis, you can't do that in AJAX calls since that line will be execute before the function has completed. Instead you have to pass in one or more callback functions that get called once the function completes.
I typically just pass in two functions that break into the JavaScript debugger of my browser when I first start figuring out such a method:
Tridion.Web.UI.ContentManager.WhereUsed.GetListUsedItems(
"tcm:1-23",
filter.conditions,
filter.columns,
new function() { console.log(arguments); debugger; },
new function() { console.log(arguments); debugger; }
);
So the first (anonymous) function is called when the (asynchronous) HTTP call to the TCM server succeeded, while the second is called when the call failed. In the answer I linked above, we called them onSuccess and onFailure to make their nature more explicit.
Both functions in this case simply write the implicit arguments parameter that is always passed in JavaScript. They then break into the JavaScript debugger of your browser, so you can inspect the arguments further.

nodeJS waiting for function to return before continuing

Just having some issues with building a function using nodeJS. It doesn't seem to be returning the value, and I have a feeling its due to the asynchronous nature of the function call. Any help would be really appreciated!
sendFilesToDB is sent an array (from fs.readdir) of files to be processed. The files' content is used to construct a SQL query. Once successfully inserted, the file is deleted. But the fileToQuery function does not return a string at all (gives a 'Argument must be a string' error).
fileToQuery is returning void, because it says:
function fileToQuery(filePath) {
do_something(...);
}
That return statement you have isn't returning from filesToQuery but from the anonymous function you defined it in.
You need to rewrite your fileToQuery function to take an extra argument (perhaps resultCallback) and instead of returning your sql string, you do:
return resultCallback("INSERT IGNORE ....");
You'll then call it like this:
fileToQuery(file,function(query){
client.query(query, function(err, results) {
fs.unlink(file, function(err) {
sendFilesToDB(files);
});
});
});
By the way: This is called "continuation passing style" and can be done in any language that supports anonymous functions. What you asked for is called a callable continuation, but not very many languages have them. If you're interested in learning about them you should try picking up scheme.

Resources