admin管理员组

文章数量:1336398

I hope someone might help me understanding the interactivity of js prototypes and jest.spOn().

I have a small Example: An example class in file TestObj.ts:

export default class TestObj {
  foo() {
    // Do Something e.g.
    console.log("Hello World!");
  }
}

The following example Test Case is succeeding but the console.log is never executed.

import TestObj from './TestObj';

const spyObj = jest.spyOn(TestObj.prototype, 'foo');
test('debug test', () => {
  const obj = new TestObj();
  obj.foo();
  expect(spyObj).toHaveBeenCalled();
});

If I change the Example Test Case to the following, the test succeeds and the console.log statement is called as expected.

import TestObj from './TestObj';

test('debug test', () => {
  const spyObj = jest.spyOn(TestObj.prototype, 'foo');
  const obj = new TestObj();
  obj.foo();
  expect(spyObj).toHaveBeenCalled();
});

Any idea why the version, using the global spyOn variable does not work as expected?


Edit:

It seems to be not related to prototypes. The same Issue is there for a function without any kind of class, editing the First Code snippet (TestObj.ts) to this:

export foo() {
  // Do Something e.g.
  console.log("Hello World!");
};

We receve the same issue for the updated second snipped. (The test succeeds but the console log is never reached.)

import * as testlib from './TestObj';

const spyObj = jest.spyOn(testlib, 'foo');
test('debug test', () => {
  testlib.foo();
  expect(spyObj).toHaveBeenCalled();
});

However if we update the second snippet to the following the test succeeds and the console log is executed:

import * as testlib from './TestObj';

const spyObj: jest.SpyInstance;

beforeEach(() => {
  spyObj = jest.spyOn(testlib, 'foo');
});
test('debug test', () => {
  testlib.foo();
  expect(spyObj).toHaveBeenCalled();
});

However I have still no clue why I discover this issue.

I hope someone might help me understanding the interactivity of js prototypes and jest.spOn().

I have a small Example: An example class in file TestObj.ts:

export default class TestObj {
  foo() {
    // Do Something e.g.
    console.log("Hello World!");
  }
}

The following example Test Case is succeeding but the console.log is never executed.

import TestObj from './TestObj';

const spyObj = jest.spyOn(TestObj.prototype, 'foo');
test('debug test', () => {
  const obj = new TestObj();
  obj.foo();
  expect(spyObj).toHaveBeenCalled();
});

If I change the Example Test Case to the following, the test succeeds and the console.log statement is called as expected.

import TestObj from './TestObj';

test('debug test', () => {
  const spyObj = jest.spyOn(TestObj.prototype, 'foo');
  const obj = new TestObj();
  obj.foo();
  expect(spyObj).toHaveBeenCalled();
});

Any idea why the version, using the global spyOn variable does not work as expected?


Edit:

It seems to be not related to prototypes. The same Issue is there for a function without any kind of class, editing the First Code snippet (TestObj.ts) to this:

export foo() {
  // Do Something e.g.
  console.log("Hello World!");
};

We receve the same issue for the updated second snipped. (The test succeeds but the console log is never reached.)

import * as testlib from './TestObj';

const spyObj = jest.spyOn(testlib, 'foo');
test('debug test', () => {
  testlib.foo();
  expect(spyObj).toHaveBeenCalled();
});

However if we update the second snippet to the following the test succeeds and the console log is executed:

import * as testlib from './TestObj';

const spyObj: jest.SpyInstance;

beforeEach(() => {
  spyObj = jest.spyOn(testlib, 'foo');
});
test('debug test', () => {
  testlib.foo();
  expect(spyObj).toHaveBeenCalled();
});

However I have still no clue why I discover this issue.

Share Improve this question edited Sep 7, 2021 at 9:08 lubro asked Sep 6, 2021 at 17:24 lubrolubro 3561 gold badge4 silver badges11 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 6

who ever es across this post,

Problem explanation

I did a lot more of research(try&error, mdn, jest manpage and a lot of medium articles) and I guess that I found out the reason for the strange behavior. To understand this issue it is important to know a number of points:

  • 1: JS prototypes are global variables, every object of the corresponding type relies on.
  • 2: Jest does not reset global variables after every test, changes made before, or inside any test to a global variable will be kept for the whole test suite (file).
  • 3: The jest spy on function is actually a mockup of the specified function with an implementation calling the function it self. e.g.: jest.SpyOn(TestObj.prototype, 'foo'); actually is implemented as: TestObj.prototype.foo = new jest.fn().mockImplementation(()=>{original_TestObj.prototype.foo()}); This means spying on a function of a class prototype is actually changing a global variable.
  • 4: Depending on your jest config, there is the possibility to reset mockup functions before every test to the default value. But be careful the default function for spyOn seems to be the same as for jest.fn() it self, an empty implementation, which means the mock-up is still callable but no code, especially not the original implementation is executed.

Solution

  • avoid changing global variables, if you want your testcases independent from each other.
  • avoid spying on prototypes, in test cases, if you require the spy only in a single test try to spy on the local object eg:
test('should foo', () => {
  const testObj = new TestObj();
  const spyOnFn = jest.spyOn(testObj, 'foo');

  // Do anything 

  expect(spyOnFn).to//Have been anything
});
  • if requiring spyOn implementations of the same function in more than one test try to create a global variable for the Test but use the before each functionality of jest to setup the Spy. This functionality is executed after the reset of all mocks (if enabled). e.g.:
let spyOnFunction1: jest.SpyInstance;

beforeEach(()=> {
  spyOnFunction1 = jest.spyOn(TestObj.prototype, 'foo');
});

本文标签: javascriptglobal Jest SpyOn function doesen39t call the original functionStack Overflow