Why does mutating a module update the reference if calling that module from another module, but not if calling from itself?

dwj*_*ton 5 javascript jestjs es6-modules

This question pertains to testing javascript and mocking functions.

Say I have a module that looks like this:

export function alpha(n) {
    return `${n}${beta(n)}${n}`;
}

export function beta(n) {
    return new Array(n).fill(0).map(() => ".").join("");
}
Run Code Online (Sandbox Code Playgroud)

Then I can't test it the following way:

import * as indexModule from "./index";

//Not what we want to do, because we want to mock the functionality of beta
describe("alpha, large test", () => {
    it("alpha(1) returns '1.1'", () => {
        expect(indexModule.alpha(1)).toEqual("1.1"); //PASS
    });

    it("alpha(3) returns '3...3'", () => {
        expect(indexModule.alpha(3)).toEqual("3...3"); //PASS
    });
});

//Simple atomic test
describe("beta", () => {
    it("beta(3) returns '...'", () => {
        expect(indexModule.beta(3)).toEqual("..."); //FAIL: received: 'x'
    });
});

//Here we are trying to mutate the beta function to mock its functionality
describe("alpha", () => {

    indexModule.beta = (n) => "x";
    it("works", () => {
        expect(indexModule.alpha(3)).toEqual("3x3"); //FAIL, recieved: '3...3'
    });
});
Run Code Online (Sandbox Code Playgroud)

However, if split the module into two:

alpha.js

import { beta } from "./beta";

export function alpha(n) {
    return `${n}${beta(n)}${n}`;
}
Run Code Online (Sandbox Code Playgroud)

beta.js

export function beta(n) {
    return new Array(n).fill(0).map(() => ".").join("");
}
Run Code Online (Sandbox Code Playgroud)

Then I can mutate the beta module, and alpha knows about it:

import { alpha } from "./alpha";
import * as betaModule from "./beta";

describe("alpha", () => {
    betaModule.beta = (n) => "x";
    it("works", () => {
        expect(alpha(3)).toEqual("3x3");   //PASS
    });
});
Run Code Online (Sandbox Code Playgroud)

Why is this the case? I'm looking for a technically specific answer.

I have a Github branch with this code here, see the mutateModule and singleFunctionPerModuleAndMutate folders.

As an additional question - in this example I am mutating the module by directly reassigning properties. Am I right in understanding that using jest mock functionality is going to be essentially doing the same thing?

ie. If the reason that the first example doesn't work but the second doesn't is due to the mutation, then it necceserily means that using the jest module mocking functions is similarly not going to work.

As far as I know - there is not way to mock a single function in a module, while testing that module, as this jest github issues talks about. What I'm wanting to know - is why this is.

Bri*_*ams 5

为什么如果从另一个模块调用该模块,则改变模块会更新引用,但如果从自身调用则不会?

“在 ES6 中,导入是导出值的实时只读视图”

当您导入 ES6 模块时,您实际上可以实时查看该模块导出的内容。

实时视图可以发生变化,任何导入模块导出的实时视图的代码都会看到变化。

这就是为什么您的测试在alphabeta位于两个不同的模块中时有效。测试修改了beta模块的实时视图,由于alpha模块使用了模块的实时视图beta,因此它自动使用模拟的函数而不是原来的函数。

另一方面,在上面的代码中alphabeta位于同一模块中并直接alpha调用beta. alpha 使用模块的实时视图,因此当测试修改模块的实时视图时没有任何效果。


作为一个附加问题 - 在此示例中,我通过直接重新分配属性来改变模块。我是否正确理解使用笑话模拟功能本质上会做同样的事情?

有几种方法可以使用Jest.

其中一种方法是使用jest.spyOn它接受一个对象和一个方法名称,并用调用原始方法的间谍替换对象上的方法

一种常见的使用方法jest.spyOn是将 ES6 模块的实时视图作为改变模块实时视图的对象传递给它。

所以是的,通过将 ES6 模块的实时视图传递给类似jest.spyOn(或spyOnfrom Jasmine、或sinon.spyfromSinon等)的东西来进行模拟,会改变模块的实时视图,其方式与直接改变模块的实时视图基本相同,就像你一样在上面的代码中执行。


据我所知 - 在测试该模块时,没有办法模拟模块中的单个函数,正如这个笑话 github issues 所讨论的那样。我想知道的是为什么会这样。

事实上,这可能的。

“ES6模块自动支持循环依赖”,这意味着模块的实时视图可以导入到模块本身中

只要使用定义的模块的实时视图进行alpha调用,就可以在测试期间进行模拟。即使它们是在同一模块中定义的,这也有效:betabetabeta

import * as indexModule from './index'  // import the live view of the module

export function alpha(n) {
    return `${n}${indexModule.beta(n)}${n}`;  // call beta using the live view of the module
}

export function beta(n) {
    return new Array(n).fill(0).map(() => ".").join("");
}
Run Code Online (Sandbox Code Playgroud)