javascript - 如何在JS中测试工厂方法?

标签 javascript node.js unit-testing mocha.js sinon

背景

我在 JS 中有一个工厂方法,它为我的 node.js 应用程序创建一个对象。这个工厂方法接收一些参数,我想测试我是否正确地创建了对象。

代码

const LibX = require("libX");

const obj = deps => {

    const { colorLib } = deps;

    const hello = () => {
        console.log(colorLib.sayHello()); // prints a phrase with cool colors
    };

    return {
        hello
    };
};

//Here I return `obj` with all dependencies included. Ready to use!
const objFactory = ({animal, owner = "max"}) => {

    //For example,I need to know if phrase is being well constructed
    const phrase = `${owner} from ${animal} says hello!`;

    const lib = new LibX(phrase);
    return obj({ colorLib: lib });
};

const myObj = objFactory({animal: "cat"});
myObj.hello();

问题

obj 函数很容易测试,因为我传递了一个对象中的所有依赖项,因此我可以 stub 和监视我想要的一切。

问题是 objFactory,这个函数应该创建一个包含所有内容的 obj 对象,为了做到这一点,我使用了 new LibX 那里,这意味着我不能 mock 它。我也无法测试 phrase 是否构建良好或是否正确传递。

这也违反了 Law of Demeter因为我的工厂需要知道一些它不应该知道的事情。

没有将 LibX 作为参数传递(这意味着我的工厂需要一个工厂......令人困惑吧?)我不知道如何解决这个问题。

问题

如何使 objFactory 易于测试?

最佳答案

您需要问自己的第一个问题是您要测试什么。

您是否需要确保正确构建 phrase 常量?如果是这样,您需要将其提取到一个单独的函数中并单独进行测试。

或者您可能想要测试 myObj.hello(); 的效果。在这种情况下,我建议让 hello() 返回一个字符串,而不是将其记录到控制台;这将使最终效果易于测试。

干净的代码会避免不可模拟的依赖。您编写示例的方式 libx 是外部依赖项,无法模拟。或者我应该说,它不应该被 mock 。从技术上讲,也可以模拟它,但我建议不要这样做,因为它会带来自身的复杂性。

1。确保短语构建正确

这很简单。您的单元测试应该看起来像这样:

it("should build the phrase correctly using all params", () => {
    // given
    const input = {animal: "dog", owner: "joe"};

    // when
    const result = buildPhrase(input);

    // then
    expect(result).to.equal("joe from dog says hello!");
});

it("should build the phrase correctly using only required params", () => {
    // given
    const input = {animal: "cat"};

    // when
    const result = buildPhrase(input);

    // then
    expect(result).to.equal("max from cat says hello!");
});

通过上述单元测试,您的生产代码需要看起来像这样:

const buildPhrase = function(input) {
    const owner = input.owner || "max";
    const animal = input.animal;

    return `${owner} from ${animal} says hello!`;
};

好了,短语构建已经过测试。然后,您可以在 objFactory 中使用 buildPhrase

2。返回方法测试效果

这也很简单。您向工厂提供输入并期望得到输出。输出将始终是输入的函数,即相同的输入将始终产生相同的输出。那么,如果您可以预测预期结果,为什么还要测试幕后发生的事情?

it("should produce a function that returns correct greeting", () => {
    // given
    const input = {animal: "cat"};
    const obj = objFactory(input);

    // when
    const result = obj.hello();

    // then
    expect(result).to.equal("max from cat says hello!");
});

这最终可能会导致您使用以下生产代码:

const LibX = require("libX");

const obj = deps => {
    const { colorLib } = deps;
    const hello = () => {
        return colorLib.sayHello(); // note the change here
    };

    return {hello};
};

const objFactory = ({animal, owner = "max"}) => {
    const phrase = `${owner} from ${animal} says hello!`;
    const lib = new LibX(phrase);

    return obj({ colorLib: lib });
};

3。模拟 require("libx")

的输出

或者不要。如前所述,您真的不应该这样做。不过,如果您被迫这样做(我将此决定背后的原因留给您),您可以使用诸如 mock-require 或类似工具之类的工具。

const mock = require("mock-require");

let currentPhrase;
mock("libx", function(phrase) {
    currentPhrase = phrase;
    this.sayHello = function() {};
});

const objFactory = require("./objFactory");

describe("objFactory", () => {
    it("should pass correct phrase to libx", () => {
        // given
        const input = {animal: "cat"};

        // when
        objFactory(input);

        // then
        expect(currentPhrase).to.be("max from cat says hello!");
    });
});

但是请记住,这种方法比看起来更棘手。模拟 require 依赖项会覆盖 require 的缓存,因此您必须记住清除它,以防其他测试不希望依赖项被模拟,而是依赖于它做什么确实如此。此外,您必须始终保持警惕并确保代码的执行顺序(并不总是那么明显)是正确的。您必须先模拟依赖关系,然后使用 require(),但确保这一点并不总是那么容易。

4。只需注入(inject)依赖

模拟依赖项的最简单方法始终是注入(inject)它。由于您在代码中使用了 new,因此将其包装在一个您可以随时模拟的简单函数中可能很有意义:

const makeLibx = (phrase) => {
    return new LibX(phrase);
};

如果你随后将其注入(inject)你的工厂,模拟将变得微不足道:

it("should pass correct input to libx", () => {
    // given
    let phrase;
    const mockMakeLibx = function(_phrase) {
        phrase = _phrase;
        return {sayHello() {}};
    };
    const input = {animal: "cat"};

    // when
    objFactory(mockMakeLibx, input);

    // then
    expect(phrase).to.equal("max from cat says hello!");
});

这显然会导致您编写如下内容:

const objFactory = (makeLibx, {animal, owner = "max"}) => {
    const phrase = `${owner} from ${animal} says hello!`;
    const lib = makeLibx(phrase);

    return obj({ colorLib: lib });
};

我的最后一条建议:始终提前计划您的代码并尽可能使用 TDD。如果您编写生产代码然后考虑如何测试它,您会发现自己一遍又一遍地问同样的问题:我如何测试它?我如何模拟这种依赖?这不违反得墨忒耳法则吗?

虽然您应该问自己的问题是:我希望这段代码做什么?我希望它表现如何?它的效果应该是怎样的?

关于javascript - 如何在JS中测试工厂方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46295471/

相关文章:

javascript - 如何通过更改页面方向从 JavaScript 生成 Word 文档? (纵向到横向)

node.js - 使用node.js集群时,worker挂掉时如何访问它的环境?

node.js - Node : 'multer' file upload callback function

unit-testing - Angular 2 单元测试 : Can't resolve all parameters for Router

javascript - 如何在类似于 GWT 的 DeferredCommand 的事件循环之外执行延迟的 javascript

javascript - 不变违规 : Invariant Violation: requireNativeComponent: "RNCSafeAreaView" was not found in the UIManager. "

javascript - 如何使用 HTTP POST 请求将参数发送到 Iframe

node.js - Node rsa : InvalidAsn1Error: encoding too long

sql-server - tSQLt:在 SetUp 和测试之间共享数据

java - 如何仅模拟函数的一部分