Using Jasmine to spy on a function without an object - javascript

I'm using Jasmine and have a library js file with lots of functions which are not associated with any object (i.e. are global). How do I go about spying on these functions?
I tried using window/document as the object, but the spy did not work even though the function was called. I also tried wrapping it in a fake object as follows :
var fakeElement = {};
fakeElement.fakeMethod = myFunctionName;
spyOn(fakeElement, "fakeMethod");
and test with
expect(fakeElement.fakeMethod).toHaveBeenCalled();
This does not work either as the spy did not work.

If you are defining your function:
function test() {};
Then, this is equivalent to:
window.test = function() {} /* (in the browser) */
So spyOn(window, 'test') should work.
If that is not, you should also be able to:
test = jasmine.createSpy();
If none of those are working, something else is going on with your setup.
I don't think your fakeElement technique works because of what is going on behind the scenes. The original globalMethod still points to the same code. What spying does is proxy it, but only in the context of an object. If you can get your test code to call through the fakeElement it would work, but then you'd be able to give up global fns.

TypeScript users:
I know the OP asked about javascript, but for any TypeScript users who come across this who want to spy on an imported function, here's what you can do.
In the test file, convert the import of the function from this:
import {foo} from '../foo_functions';
x = foo(y);
To this:
import * as FooFunctions from '../foo_functions';
x = FooFunctions.foo(y);
Then you can spy on FooFunctions.foo :)
spyOn(FooFunctions, 'foo').and.callFake(...);
// ...
expect(FooFunctions.foo).toHaveBeenCalled();

There is 2 alternative which I use (for jasmine 2)
This one is not quite explicit because it seems that the function is actually a fake.
test = createSpy().and.callFake(test);
The second more verbose, more explicit, and "cleaner":
test = createSpy('testSpy', test).and.callThrough();
-> jasmine source code to see the second argument

A very simple way:
import * as myFunctionContainer from 'whatever-lib';
const fooSpy = spyOn(myFunctionContainer, 'myFunc');

Solution with TypeScript
Replace your import:
import { yourGlobalFunction } from '../your-global-functions';
with:
import * as Functions from '../your-global-functions';
Then:
spyOnProperty(Functions, 'yourGlobalFunction').and.returnValue(() => 'mock return value');

import * as saveAsFunctions from 'file-saver';
..........
.......
let saveAs;
beforeEach(() => {
saveAs = jasmine.createSpy('saveAs');
})
it('should generate the excel on sample request details page', () => {
spyOn(saveAsFunctions, 'saveAs').and.callFake(saveAs);
expect(saveAsFunctions.saveAs).toHaveBeenCalled();
})
This worked for me.

The approach we usually follow, is as follows:
utils.ts file for all global utilities:
function globalUtil() {
// some code
}
abc.component.ts:
function foo {
// some code
globalUtil(); // calls global function from util.ts
}
While writing a Jasmine test for function foo (), you can spy on the globalUtil function as follows:
abc.component.spec.ts:
import * as SharedUtilities from 'util.ts';
it('foo', () =>
{
const globalUtilSpy = jasmine.createSpy('globalUtilSpy');
spyOnProperty(SharedUtilities, "globalUtilSpy").and.returnValue(globalUtilSpy);
foo();
expect(globalUtilSpy).toHaveBeenCalled();
});

My answer differs slightly to #FlavorScape in that I had a single (default export) function in the imported module, I did the following:
import * as functionToTest from 'whatever-lib';
const fooSpy = spyOn(functionToTest, 'default');

I guess it's the easiest way:
const funcSpy = spyOn(myFunc, 'call');

Related

Is there any way to stub these type of functions?

There is a file helperFunction.js, which looks like:
module.exports = (arg1, arg2) => {
\\function body
}
Now, in file.js, this function can be simply called by:
let helperFunction = require('./helperFunction.js');
//some code here
let a=1, b=2;
let val = helperFunction(a,b);
//some code here
In order to test file.js, I want to stub the helperFunction. However, the syntax for sinon.stub looks like:
let functionStub = sinon.stub(file, "functionName");
And here in my case the fileName itself is the function name. How do I create a stub for helperFunction now? Or is there anything else I can do?
You can use a library like proxyquire which can be used to override dependencies during testing.
That would mean you would end up with something like this:
const helper = sinon.stub();
const moduleToTest = proxyquire('./your-file-name’, {
'./helperFunction': helper,
});
Although if you do not want to add a new library, you can always switch to refactoring the helperFunction.js file and exporting your function as a named export instead of default export. That will give you an object having the method you need to stub and that would fit in nicely with your current approach

Monkey-patching jest object for all test files

How can I monkey-patch some methods in the global jest object for all test files at once? I don't want to add any extra code to my test files, it has to be done somewhere in setup and it can be an ugly hack.
I tried doing that from a custom environment, setupFiles and setupFilesAfterEnv, but it looks like they all get a different instance of jest object and my changes aren't visible in test files.
Disclaimer: I know that it's a bad thing to do but I need it for some one-time benchmarking only and it's the easiest solution that gets the job done.
I got this working! You're right, Jest does re-construct the global jest object for every test case, but you can override the function it uses to do that. In jest.config.js, set globalSetup to something like <rootDir>/jest-global-setup.js. Then, in jest-global-setup.js, add this:
const jestRuntime = require('jest-runtime');
const { _createJestObjectFor } = jestRuntime.prototype;
jestRuntime.prototype._createJestObjectFor = function(...args) {
// Call the original function to create a normal jest object.
const jestObject = _createJestObjectFor.apply(this, args);
// Apply your changes.
jestObject.isMonkeyPatched = true;
// Return the patched object.
return jestObject;
}
// Jest expects to find a function of some sort as well,
// but we don't need it for this example.
module.exports = function() { /* do nothing */ }

Is it possible to have these functions exposed for testing?

Given that I'm extending an existing module and it uses the module.exports in the way shown below, can I even call the start methods from (mocha) tests?
I suspect that there's no decent way to tap into it - and that's ok. I'd just rather test these if I am able to and would love to know how to do it if possible.
const NodeHelper = require("node_helper");
module.exports = NodeHelper.create({
start: function() {
//do stuff
};
});
Edit: NodeHelper returns a function that appears to be "extended":
NodeHelper.create = function(moduleDefinition) {
return NodeHelper.extend(moduleDefinition);
};
Looking closer at the linked code, it uses Resig's class.js so you probably need to create an instance to call the prototype methods, ie
const YourNodeHelper = require('path/to/your/module')
const yourNodeHelper = new YourNodeHelper() // create instance here
yourNodeHelper.start()

Sinon spy in a node app not working on a method called from a method inside same file

Let's say, I have app.js
function method(){
return true;
}
function callMethod(){
return method();
}
module.exports.method = method;
module.exports.callMethod = callMethod;
And app.spec.js
var app = require(app);
...describe code etc...
var first = sinon.spy(app,'callMethod');
var second = sinon.spy(app,'method');
app.callMethod();
expect(first.called).to.equal(true); //passed
expect(second.called).to.equal(true); //fails
I'm guessing it's failing because inside my spec file I have a different reference to 'method' than the one that is being called when inside app.js calls it from inside of 'callMethod'.
I've seen this behavior many times before, and I know work arounds, but I was wondering if there was a clean way to actually spy on the method 'method' in this situation without a workaround.

Is i possible to call t() instead of I18n.t() in javascript?

I am using i18n-js to load locales in to my js app. When I use the translate function in my view I would like to be able to just say t('Hello') instead of I18n.t('Hello'). How can I get such an alias to work?
import I18n from 'i18n';
const Hello = () => {
return (
<div>{I18n.t("hello", {name: "John"})}</div>
)
}
You could assign the t function to window:
window.t = I18n.t;
Then you can just use t('Hello'). However, as #Thilo mentioned, this may result in some complications.
A better alternative is to write a small wrapper:
function t(key){
return I18n.t(key);
}
The best option, suggested by #deceze, would be this single line:
var t = I18n.t.bind(I18n);
Single-line, and should work regardless of the scope it's called in.

Categories