繁体   English   中英

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

[英]How to test factory method in JS?

背景

我在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函数很容易测试,因为我将所有依赖项传递给对象,因此可以存根和监视所有想要的东西。

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

这也违反了得墨 the 耳定律,因为我的工厂需要知道一些本不应该知道的东西。

如果不传递LibX作为参数(这意味着我将需要一个Factory作为工厂....造成混淆了吗?),我不知道如何解决此问题。

如何使objFactory易于测试?

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

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

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

编写清晰的代码将避免产生不可模仿的依赖关系。 不能嘲笑您编写示例的方法libx (它是一个外部依赖项)。 或者我应该说,它不应该被嘲笑。 从技术上讲,也可以对其进行模拟,但是我建议您不要这样做,因为它会给图片带来其自身的复杂性。

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!`;
};

在这里,短语构建已经过测试。 然后,您可以使用buildPhrase你里面objFactory

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")的输出

还是不。 如前所述,您实际上不应该这样做。 不过,如果您被迫这样做(我将推理留给了这个决定),则可以使用模拟需求或类似的工具。

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.注入依赖

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

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

如果您随后将其注入到工厂中,则嘲笑将变得微不足道:

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。 如果您编写生产代码然后考虑如何进行测试,就会发现自己一遍又一遍地问同样的问题:我该如何测试它? 我该如何模拟这种依赖性? 这不违反得墨meter耳定律吗?

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM