测试第三方类的工厂

时间:2013-08-23 14:30:16

标签: java unit-testing guice mockito factory

我的应用程序使用第三方jar(无法访问源等)我有一个工厂,可以从设置中正确创建一个对象(称为Foo),即

public FooFactoryImpl implements FooFactory {
    private final Settings settings;
    private final OtherDependency other;

    @Inject
    public FooFactoryImpl(Settings settings, OtherDependency other) {
        this.settings = settings;
        this.other = other;
    }

    public Foo create(String theirArg) {
        Foo newFoo = new Foo(theirArg); // there is no no-arg constructor

        // This isn't exactly the way I do it but this is shorter and close enough
        newFoo.setParamOne(settings.get("ParamOne")); 
        newFoo.setParamTwo(settings.get("ParamTwo"));
        // etc.
    }
}

我想使用Mockito对此工厂进行单元测试 - 确保已正确配置创建的对象。但当然,我遇到this problem;也就是说,因为我的工厂叫new,我不能注入间谍。

一种可能的解决方案是引入类似的东西:

public FooFactoryDumb implements FooFactory {
    public Foo create(String theirArg) {
        return new Foo(theirArg);
    }
}

然后像:

public FooFactoryImpl implements FooFactory {
    @Inject @Dumb private FooFactory inner;

    // snip, see above

    public create(String theirArg) {
        Foo newFoo = inner.create(theirArg);
        // etc.
    }
}

这似乎是很多样板代码只是为了启用单元测试。对我而言smells bad,但我可能错了。还有更好的方法吗?

4 个答案:

答案 0 :(得分:2)

有一种类似但更简单的方法:向工厂添加一个受保护的方法来创建一个Foo:

protected Foo create(String theirArg){
    return new Foo(theirArg);
}

然后在您的Factory测试中,创建FactoryImpl的Test Double并覆盖create方法:

private class FooFactoryImplTestDouble extends FooFactoryImpl{
    ...
    @Override
    protected Foo create(String theirArg){

        //create and return your spy here
    }
}

答案 1 :(得分:0)

创建一个新类:

public class FooFactory3rd {
    public Foo create3rdParty(String theirArg) {
        return new Foo(theirArg);
    }
}

然后将您的课程更改为:

public FooFactoryImpl implements FooFactory {
    private final Settings settings;
    private final OtherDependency other;
    private final FooFactory3rd fooFactory3rd;

    @Inject
    public FooFactoryImpl(Settings settings, OtherDependency other, FooFactory3rd fooFactory3rd) {
        this.settings = settings;
        this.other = other;
        this.fooFactory3rd = fooFactory3rd;
    }

    public Foo create(String theirArg) {
        Foo newFoo = fooFactory3rd.create3rdParty(theirArg);

        // This isn't exactly the way I do it but this is shorter and close enough
        newFoo.setParamOne(settings.get("ParamOne")); 
        newFoo.setParamTwo(settings.get("ParamTwo"));
        // etc.
    }
}

在您的测试代码中:

Foo fooMock = mock(Foo.class);
FooFactory3rd fooFactory3rdMock = mock(FooFactory3rd.class);
when(fooFactory3rdMock.create3rdParty(any(String.class)).thenReturn(fooMock);

FooFactoryImpl fooFactoryImpl = new FooFactoryImpl(settings, other, fooFactory3rdMock);
fooFactoryImpl.create("any string");

这样,你可以注入你的fooMock。当你致电fooFactoryImpl.create("any string")时,你的模拟Foo会在封面下被召唤。

或者如果你想进一步清理,甚至不需要FooFactory3rd的构造函数arg。只需声明

    private final FooFactory3rd fooFactory3rd = new FooFactory3rd();

在测试中,使用反射将其更改为模拟的FooFactory3rd。

答案 2 :(得分:0)

嗯,事实证明我必须使用PowerMock,因为第三方的方法是最终的。由于我已经在使用PowerMock,我意识到我可以这样做:

@Before
public void setUp() throws Exception {
    Foo toReturn = PowerMockito.mock(Foo.class);
    PowerMockito.whenNew(Foo.class).withAnyArguments().thenReturn(toReturn);
}

然后我根本不需要触摸原来的课程。

注意:如果你这样做,你必须为PowerMock准备两个类,即

@PrepareForTest( { Foo.class, FooFactoryImpl.class } )

答案 3 :(得分:0)

退一步思考FooFactoryImpl的合同是什么。无论如何,它必须创建一个功能齐全的Foo。因此,如果Foo的合约是X,Y和Z,那么FooFactoryImpl的合约就是它创建了做X,Y和Z的对象。

这是SUT由多个类组成的测试类型。我不在乎你是将它称为单元测试,集成测试,子系统测试,协作测试还是其他名称。关键是FooFactoryImpl唯一有意义的测试是测试Foo的测试。不是单独为Foo编写测试类,而是编写一个测试类来共同测试这两个类。

因此,如果Foo的合同是X,Y和Z,那么您的测试用例将使用FooFactoryImpl执行以下操作。

  • 调用create并测试创建的对象是否为X。
  • 调用create并测试创建的对象是否为Y。
  • 调用create并测试创建的对象是否为Z。

我相信这是解决这个问题的唯一合理方法。困难的部分是为测试课提供一个令人信服的名字。