Spy on an attribute/function of a private variable with Jasmine
Jasmine, as far as I know, doesn't use any compiler-kind magic, so it's impossible for Jasmine to get access to you private variables.
-
From a client's point of view,
doThing()
is the only function it cares about. Thus, it's the only one exported by this file.but this doesn't mean that you should deprive your tests from the access to over staff. Instead you can create two files
file1.ts
- for a clientimport { doThing } from "./file1_implementation" export doThing
and
file1_implementation.ts
- for your testsexport function f1(...) ... export function f2(...) ... export function f3(...) ... export const myMap ... export function doThing(...) ...
then in
file1.spec.ts
you can usefile1_implementation.ts
and you'll have access to everything you needimport * as file1 from '../src/file1_implementation' ...
General idea: use rewire
.
Using rewire
, we will override your private functions with spy
functions.
However, your const myMap
needs to be modified. Because when you do ['key1', f1]
- it stores current implementation of f1
, so we can't override it after myMap
was initialized. One of the ways to overcome this - use ['key1', args => f1(args)]
. This way, it will not store f1
function, only the wrapper to call it. You might achieve the same by using apply()
or call()
.
Example implementation:
file1.ts
:
function f1(): number {
// do far-reaching things
return 1;
}
const myMap: Map<string, (x: number) => number> = new Map([
['key1', (...args: Parameters<typeof f1>) => f1(...args)],
]);
export function doThing(): number {
const key = 'key1';
const magicNumber = 7;
const fileToExecute = myMap.get(key);
return fileToExecute(magicNumber);
}
file1.spec.ts
:
import * as rewire from 'rewire';
it('calls the correct fake method', () => {
const spies = [jasmine.createSpy('f1spy').and.returnValue(4)];
const myModule = rewire('./file1');
myModule.__set__('f1', spies[0]);
myModule.doThing();
expect(spies[0]).toHaveBeenCalledWith(7);
});
In order to use rewire
with typescript, you might want to use babel, etc.
For proof of concept, I'm just going to compile it:
./node_modules/.bin/tsc rewire-example/*
and run tests:
./node_modules/.bin/jasmine rewire-example/file1.spec.js
Which will run successfully:
Started
.
1 spec, 0 failures
Without modifications to myMap
:
file1.spec.ts
:
import * as rewire from 'rewire';
it('calls the correct fake method', () => {
const spies = [
jasmine.createSpy('f1spy').and.returnValue(4),
jasmine.createSpy('f2spy').and.returnValue(5),
// ...
];
const myModule = rewire('./file1');
const myMockedMap: Map<string, (x: number) => number> = new Map();
(myModule.__get__('myMap') as typeof myMockedMap).forEach((value, key) =>
myMockedMap.set(key, value)
);
myModule.__set__('myMap', myMockedMap);
// ...
});
Can you just make file1 into a class? Then you can definitely access its private methods / attributes from jasmine.
so file1 becomes:
export class FileHelper {
private f1 () : void {}
private f2 () : void {}
private f3 () : void {}
private myMap: Map<whatever, whatever>;
public doThing () : void {}
}
then in your spec:
let mapSpy: jasmine.Spy;
let myFileHelper: FileHelper;
beforeEach(() => {
myFileHelper = new FileHelper();
mapSpy = spyOn(<any>myFileHelper, 'myMap').and.callFake(() => {
//whatever you were doing
});
});
it('should do whatever', () => {
});