Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SpyOn individually exported ES6 functions

tl;dr:

  1. I use Jasmine;
  2. I want to test aaa function which called bbb from the same module;
  3. I want to spy on bbb, but eventually aaa called the original bbb function, not a spy;

How can I force aaa to call the spy?

The Module:

export function aaa() {
  return bbb();
}

export function bbb() {
  return 222;
}

The test:

import * as util from 'my-module';

describe('aaa test', () => {

  let bbbSpy: Spy;

  beforeEach(() => {
    bbbSpy = spyOn(util, 'bbb');
  });

  it('should return SPYED', () => {
    bbbSpy.and.returnValue('SPYED!!');
    const result = util.aaa();
    expect(result).toEqual('SPYED!!'); // Doesn't work - still 222
  });

});

So, basically that doesn't work. Can anyone help me please?

P.S. I don't want to change the module's code, because in that case I'll have to change tons of code in the project. I need a general solution for tests.

like image 836
Sergei Panfilov Avatar asked Mar 08 '18 12:03

Sergei Panfilov


2 Answers

The code wasn't written with testing concerns in mind, it won't get full coverage without refactoring.

It's impossible to force aaa to call the spy in this case. It refers directly to bbb function in module scope. There's no object to spy on.

It's basically the same sort of JavaScript scope problem as:

(() => {
  var bar = 1; // there's no way to reach this variable from the outside
})();

It is possible to do that if functions are consistently referred as object properties. There's no such object in ES modules but this recipe is common in CommonJS modules, especially because of testability concerns (bundling tools like Webpack support both):

exports.aaa = function aaa() {
  return exports.bbb();
}

exports.bbb = function bbb() {
  return 222;
}

This way bbb property could be spied on * import with spyOn(util, 'bbb'), as shown in original code. This doesn't apply to native ES modules that have read-only exports and don't interoperate with CommonJS modules.

It may be easier to do that if aaa and bbb reside in different modules. This way there's a possibility to mock modules (this doesn't apply to native ES modules).

like image 103
Estus Flask Avatar answered Oct 14 '22 04:10

Estus Flask


Even thought is not the same framework, there is a related question that gives you why this doesnt work: How to mock functions in the same module using jest. Basically you wont be able to access this fixed reference of the function and instead make clear that is the same function as in the context of the module.

I know this doesn't satisfy the constraint that you post in your question, but it just makes obvious that what you want to achieve is not possible by using spy.

With JavaScript, there is no way to swap out references to something. You cannot swap out a function that is internal to a module. When you try to overwrite bbb with spyOn.and.returnValue in your example, you are just modifying the local binding bbb in your test but it has no effect on the bbb binding in your other file.

like image 5
SirPeople Avatar answered Oct 14 '22 04:10

SirPeople