英文:
Attempting to mock child method, fails if parent module imports it using object destructuring
问题
我正在尝试进行一些基本的Jest单元测试,以更深入学习它。
我遇到了一个问题,我不知道如何解释。
这个文件有一个子函数add
// FileB.js
const add = (a, b) => {
return a + b;
}
module.exports = {
add,
};
这个文件有一个父函数addTen
// FileA.js
const { add } = require('./FileB');
const addTen = num => {
return add(10, num);
}
module.exports = {
addTen,
};
这是我的测试文件,我试图检查<mockedFunction/spy>.mock.calls
或者使用toHaveBeenCalledWith
来查看当我调用addTen(10)
时是否传递了内部子方法add的参数10,10。
这个文件没有在任何实际环境中使用,只是我尝试学习Jest和单元测试的工作。
// randomTest.js
const { addTen } = require('../src/FileA');
const fileB = require('../src/FileB');
describe('temp', () => {
it('temp', () => {
const addSpy = jest.spyOn(fileB, 'add');
addTen(10);
console.log(addSpy.mock.calls);
expect(addSpy).toHaveBeenCalledWith(10, 10);
});
});
现在问题是,测试失败,说add
从未被调用,或者没有传入任何参数。我在FileB
中记录了add函数。
但是,如果我以以下方式修改FileA
,即导入整个模块而不是解构,测试就会通过,并且我可以记录下函数并查看它的工作方式。
这个方法有效:
// FileA.js
const fileB = require('./FileB');
const addTen = num => {
return fileB.add(10, num);
}
module.exports = {
addTen,
};
为什么这个轻微的更改有效?是否有一种方法可以避免这种情况并保留我的解构?
英文:
I was doing some basic jest unit testing in attempt to learn it more.
I have this issue I do not know how to explain
This file has the child function add
// FileB.js
const add = (a, b) => {
return a + b;
}
module.exports = {
add,
};
This file has the parent function addTen
// FileA.js
const { add } = require('./FileB');
const addTen = num => {
return add(10, num);
}
module.exports = {
addTen,
};
this is my test file, where I am trying to either check <mockedFunction/spy>.mock.calls
or do toHaveBeenCalledWith
to see if the inner child method, add, is being passed in 10,10 when i call addTen(10);
This file is not used in any real env, its simply me trying to learn jest + unit testing more.
// randomTest.js
const { addTen } = require('../src/FileA');
const fileB = require('../src/FileB');
describe('temp', () => {
it('temp', () => {
const addSpy = jest.spyOn(fileB, 'add');
addTen(10);
console.log(addSpy.mock.calls);
expect(addSpy).toHaveBeenCalledWith(10,10)
});
});
Now for the issue, the test fails, saying add
was never called, or nothing passed in. I logged the add function within FileB
However, if I modify FileA
in this way, by importing entore module instead of destructuring, the test passes and I cna log out the functions and everything and see it works
This is what works
// FileA.js
const fileB = require('./FileB');
const addTen = num => {
return fileB.add(10, num);
}
module.exports = {
addTen,
};
Why does this slight change work? And is there a way to avoid this and keep my destrcutoring?
答案1
得分: 2
你可以通过在从FileA
导入addTen
之前设置好你的间谍来实现这一点。
const fileB = require('./FileB');
const spy = jest.spyOn(fileB, 'add');
const { addTen } = require('./FileA');
describe('temp', () => {
afterEach(() => {
// 恢复使用spyOn创建的间谍
jest.restoreAllMocks();
});
it('temp', () => {
addTen(10);
expect(spy).toHaveBeenCalledWith(10, 10);
});
});
或者,你可以使用jest.mock
来模拟./FileB
。
const mockAdd = jest.fn();
jest.mock('./FileB', () => {
return {
add: mockAdd
};
});
const { addTen } = require('./FileA');
describe('temp', () => {
it('temp', () => {
addTen(10);
expect(mockAdd).toHaveBeenCalledWith(10, 10);
});
});
在这两种情况下,你希望FileA
从FileB
导入一个模拟的函数,所以你应该在导入之前执行这个操作。
请注意,jest.mock
会被提升,因此即使你不将其放在文件的开头,它也会在运行时移到那里。
现在回到你的第一个问题:“为什么解构导入的对象会导致测试失败?”
首先,让我们记住JavaScript通过引用传递对象。这意味着以下情况:
const a = {
b: 1
}
const c = a;
c.b = 9;
console.log(a); // 将打印 { b: 9 }
这意味着当FileA
导入const { add } = require("./FileB")
时,你使用的是对函数add
的引用(在JavaScript中,函数是对象)。
这导致addTen
中已经具有真实add
函数的引用,即使你使用spyOn
来覆盖FileB.add
,也不会改写addTen
已经具有的内容。
现在,如果你在导入addTen之前运行spyOn,情况就不同了:
it("should pass", () => {
const addSpy = jest.spyOn(fileB, "add");
const { addTen } = require("../FileA");
addTen(10);
expect(addSpy).toHaveBeenCalledWith(10, 10);
});
FileB.add
的引用将被新模拟的引用替换,你可以看到这是如何在底层发生的:https://github.com/facebook/jest/blob/e2196cab463c5a3e15668b1d7663058b09b7c0ed/packages/jest-mock/src/index.ts#L1256。
英文:
You can achieve this by setting up your spy before importing addTen
from FileA
const fileB = require('./FileB');
const spy = jest.spyOn(fileB, 'add');
const {addTen} = require('./FileA');
describe('temp', () => {
afterEach(() => {
// restore the spy created with spyOn
jest.restoreAllMocks();
});
it('temp', () => {
addTen(10);
expect(spy).toHaveBeenCalledWith(10,10)
});
});
Or, you can mock ./FileB
with jest.mock
const mockAdd = jest.fn()
jest.mock('./FileB', ()=> {
return {
add: mockAdd
}
});
const {addTen} = require('./FileA');
describe('temp', () => {
it('temp', () => {
addTen(10);
expect(mockAdd).toHaveBeenCalledWith(10,10)
});
});
In both cases, you want your FileA
to import a mocked function from FileB
, so you would do that before importing it.
Not that jest.mock
would be hoisted anyways, so even if you don't put it at the beginning of your file, it would be moved there on the runtime
Now back to your first question "Why deconstructing the imported object make the test fail?"
First, let's remember that JavaScript pass Objects by reference. Which means the following:
const a = {
b: 1
}
const c = a;
c.b = 9;
console.log(a); // will print { b: 9 }
Which means when FileA
imports const { add } = require("./FileB")
you are using the reference to the function add
(functions are objects in JavaScript).
This leads to having the reference of the real add
function in addTen
, and then even if you use spyOn
to override FileB.add
you won't rewrite what addTen
already has.
Now in case you use const fileB = require("./FileB")
in FileA
, you get a reference of the object that has add
, that same object that you override with jest.spyOn
.
That also explains what happens when you run the spyOn before importing addTen:
it("should pass", () => {
const addSpy = jest.spyOn(fileB, "add");
const { addTen } = require("../FileA");
addTen(10);
expect(addSpy).toHaveBeenCalledWith(10, 10);
});
FileB.add
's reference will be replaced with the new mock's reference, see how this is happening under the hood: https://github.com/facebook/jest/blob/e2196cab463c5a3e15668b1d7663058b09b7c0ed/packages/jest-mock/src/index.ts#L1256.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论