176

I'm new to Jasmine and have just started using it. I 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

Chetter Hummin
  • 6,427
  • 8
  • 31
  • 44

8 Answers8

164

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.

ndp
  • 20,876
  • 5
  • 34
  • 50
  • 2
    It worked! I think the error I was making earlier was that I was calling the spyOn with method() instead of method. Thanks! – Chetter Hummin Mar 01 '12 at 08:09
  • 3
    I've had some problems using spyOn(window, 'test') using chutzpah for running the tests as part of our automation due to 'window' not being assigned. Using jasmine.createSpy() got around this. – Henners Sep 24 '13 at 13:43
  • 7
    jasmine.createSpy() worked perfectly for me. Thanks! – dplass Jul 24 '14 at 00:34
  • 1
    used `test = jasmine.createSpy();` to spy on angularJs `$anchroScroll` worked perfectly – Subtubes Mar 17 '15 at 21:20
  • 1
    For some reason I can't get either way to work, but it may be entirely possible that it's because I'm trying to mock up an existing window function; `$window.open(url, '_blank');` with the intention of opening a new tab (or window depending on browser setup). How should I be going about making sure it's calling this function and verifying that it's navigating to the right url regardless of the browser? – CSS Aug 19 '15 at 15:55
  • I don't think this will work if your function is inside of a closure/other function/IIFE. – Jon Gunter Mar 06 '18 at 17:11
  • @ndp, can you please help me with this: https://stackoverflow.com/questions/65657111/how-to-unit-test-an-angular-component-that-is-making-get-call-using-a-service – Tanzeel Jan 10 '21 at 19:30
93

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();
Alexander Taylor
  • 14,944
  • 11
  • 59
  • 78
  • 3
    Thanks for the TypeScript hint. Should be equally the same for ES6/Babel, but I haven't tried it. – hgoebl May 29 '17 at 12:09
  • 1
    Seems it only works if calling the function **explicitly with the alias FooFunctions**. I have a function bar() that is a factory returning baz() and want to test that baz() calls foo(). This method doesn't appear to work in that scenario. – Richard Matsen Jul 11 '17 at 22:46
  • 5
    This will work if the alias is taken inside foo_functions `export const FooFunctions = { bar, foo };` and the import in the test becomes `import { FooFunctions } from '../foo_functions'.` However, the alias still needs to be explicitly used within foo_functions private implementation for the spy to work. `const result = FooFunctions.foo(params)` // spy reports call `const result = foo(params)` // spy reports no call – Richard Matsen Jul 11 '17 at 23:28
  • 2
    Worked like a charm! Thanks, you saved me a lot of time! – SrAxi Jan 05 '18 at 13:33
  • @RichardMatsen do you find any way to resolve the scenario you mentioned? – Caxton Aug 03 '18 at 05:02
  • 20
    This is not working anymore got a `Error: : parseCookie is not declared writable or has no setter` – Ling Vu May 14 '20 at 06:58
  • 1
    I successfully used this in a situation where the class under test used `import { theFunction } from '../filewiththefunction'` and the test spec used `import * as prefix from '../filewiththefunction'`, then created a spy with `theFunctionSpy = spyOn(prefix, 'theFunction')`. If this doesn't work for someone then you may have another issue going on. (Typescript 4.3.5) – coppereyecat Jul 23 '21 at 17:02
48

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

IxDay
  • 3,587
  • 2
  • 21
  • 26
  • This makes a little more sense and breaks it out far enough to duplicate with success. +1 from me. Thanks, C§ – CSS Aug 19 '15 at 20:00
9

A very simple way:

import * as myFunctionContainer from 'whatever-lib';

const fooSpy = spyOn(myFunctionContainer, 'myFunc');
FlavorScape
  • 11,481
  • 11
  • 74
  • 115
1
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.

Sushil
  • 21
  • 1
  • 4
    Please add explanation to your answer, code by itself is not that helpful for the person asking the question if they cannot understand what is going on. – chevybow Jun 18 '18 at 18:56
1

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();
});
thinkOfaNumber
  • 2,073
  • 1
  • 23
  • 42
AkshAy Agrawal
  • 156
  • 1
  • 8
0

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');
IonicBurger
  • 4,663
  • 1
  • 38
  • 46
-1

I guess it's the easiest way:

const funcSpy = spyOn(myFunc, 'call');
onurbaysan
  • 1,230
  • 8
  • 27
Ludwig
  • 159
  • 6