Java 如何对在builder()方法中使用静态单例工厂的构建器进行单元测试?

Java 如何对在builder()方法中使用静态单例工厂的构建器进行单元测试?,java,unit-testing,factory,builder,Java,Unit Testing,Factory,Builder,我正在尝试对Selenium page对象API进行单元测试,我有一组构建器类,它们使用静态单例工厂类来实例化所需的对象。我一辈子都想不出一种方法来对builder类中的build()方法进行单元测试。如何为构建器模拟“对象创建” public class LoadableBuilder<LoadableT extends ILoadable, BeanT extends ILoadableBean> implements ILoadableBuilder<Loadab

我正在尝试对Selenium page对象API进行单元测试,我有一组构建器类,它们使用
静态
单例工厂
来实例化所需的对象。我一辈子都想不出一种方法来对builder
类中的
build()
方法进行单元测试。如何为构建器模拟“对象创建”

public class LoadableBuilder<LoadableT extends ILoadable, BeanT extends ILoadableBean>
    implements ILoadableBuilder<LoadableT, BeanT> {

    /**
     * The ILoadableBean object that specifies all the necessary information to construct an instance of the
     * ILoadable that this LoadableBuilder builds
     */
    private final @Getter @Nonnull BeanT state;

    /**
     * The class that of the ILoadable that this LoadableBuilder builds
     */
    private @Getter @Nullable Class<LoadableT> componentClass;

    public AbstractLoadableBuilder(final BeanT state) {
        this.state = state;
    }

    @Override
    public final @Nonnull LoadableBuilder setComponentClass(final Class<LoadableT> componentClass) {
        this.componentClass = componentClass;
        return this;
    }

    @Override
    public final @Nonnull LoadableBuilder setDriver(final WebDriver driver) {
        getState().setDriver(driver);
        return this;
    }

    @Override
    public final @Nonnull LoadableBuilder setLoadTimeoutInSeconds(final @Nonnegative int loadTimeoutInSeconds) {
        getState().setLoadTimeoutInSeconds(loadTimeoutInSeconds);
        return this;
    }

    @Override
    public @Nonnull LoadableT build() {
        return LoadableFactory.getInstance().create(getState(), componentClass);

    }
}
公共类LoadableBuilder
实现ILoadableBuilder{
/**
*ILoadableBean对象,指定构造
*此LoadableBuilder生成的ILoadable
*/
私有final@Getter@Nonnull BeanT state;
/**
*此LoadableBuilder构建的ILoadable的类
*/
私有@Getter@Nullable类componentClass;
公共AbstractLoadableBuilder(最终BeanT状态){
this.state=状态;
}
@凌驾
公共最终@Nonnull LoadableBuilder setComponentClass(最终类componentClass){
this.componentClass=componentClass;
归还这个;
}
@凌驾
公共最终@Nonnull LoadableBuilder setDriver(最终WebDriver){
getState().setDriver(驱动程序);
归还这个;
}
@凌驾
public final@Nonnull LoadableBuilder SetLoadTimeOutingSeconds(final@Nonnegative int LoadTimeOutingSeconds){
getState().SetLoadTimeOutingSeconds(LoadTimeOutingSeconds);
归还这个;
}
@凌驾
public@Nonnull LoadableT build(){
返回LoadableFactory.getInstance().create(getState(),componentClass);
}
}
这是工厂:

public class LoadableFactory {
    private static final class Loader {
        private static final LoadableFactory INSTANCE = new LoadableFactory();
    }

    private LoadableFactory() { }

    public static LoadableFactory getInstance() {
        return Loader.INSTANCE;
    }

    public final<BeanT extends ILoadableBean, LoadableT extends ILoadable> LoadableT create(final BeanT bean, final Class<LoadableT> componentClass) {
        final LoadableT component;

        try {
            final Constructor<LoadableT> ctor = ConstructorUtils.getMatchingAccessibleConstructor(componentClass, bean.getClass());
            component = ctor.newInstance(bean);
        } catch (InstantiationException e) {                
            throw new IllegalArgumentException("Could not instantiate an instance of " + componentClass + because it is abstract or an interface or for some other reason.");
        } catch (InvocationTargetException e) {
            throw new IllegalStateException("Could not instantiate an instance of " + componentClass + " because the constructor threw an exception. Cause: " + e.getCause() +". " + e.getMessage());
        } catch (IllegalAccessException e) {
            throw log.throwing(new IllegalArgumentException("Could not instantiate an instance of " + componentClass + " LoadableFactory does not have access to its class definition");
        }
    }
}
公共类可加载工厂{
私有静态最终类装入器{
私有静态最终LoadableFactory实例=新的LoadableFactory();
}
私有LoadableFactory(){}
公共静态LoadableFactory getInstance(){
返回Loader.INSTANCE;
}
公共最终LoadableT创建(最终BeanT bean、最终类componentClass){
最终可加载部件;
试一试{
最终构造函数ctor=ConstructorUtils.getMatchingAccessibleConstructor(componentClass,bean.getClass());
component=ctor.newInstance(bean);
}捕获(实例化异常){
抛出新的IllegalArgumentException(“无法实例化“+componentClass+的实例,因为它是抽象的、接口或其他原因”);
}捕获(调用TargetException e){
抛出新的IllegalStateException(“无法实例化“+componentClass+”的实例,因为构造函数抛出了异常。原因:“+e.getCause()+”+e.getMessage()”);
}捕获(非法访问例外e){
throw log.throwing(新的IllegalArgumentException(“无法实例化”+componentClass+“LoadableFactory无权访问其类定义”)的实例);
}
}
}

我不确定这是否是最佳实践,但我过去处理此类问题的方法是将
getInstance()
调用移动到另一个受保护的方法,然后在我的测试类中重写它。您需要让LoadableFactory实现一个接口来模拟
create()
尽管打电话:

protected ILoadableFactory getFactory(){
    return LoadableFactory.getInstance();
}
然后将您的
LoadableBuilder
类更改为使用此选项:

@Override
public @Nonnull LoadableT build() {
    return getFactory().create(getState(), componentClass);
}
然后,在
LoadableBuilderTest
类中:

public class LoadableBuilderTest extends LoadableBuilder {
    Mockery context = new Mockery();
    final LoadableFactory mockFactory = context.mock(ILoadableFactory.class); //This is the interface you'll need to make which LoadableFactory implements. It will need the create() method.

    @Override
    protected ILoadableFactory getFactory(){
        return mockFactory;
    }

    @Test
    public void shouldCallCreateProperly(){
        context.checking(new Expectations(){{
            oneOf(mockFactory).create(arg1,arg2);
        }});
        //Do your stuff to test that create() was called.
    }
}

我过去处理这个问题的一种方法是创建另一个方法,它只保存
getInstance()
调用,然后在测试中重写它(让您的测试扩展
LoadableBuilder
类)。您的
LoadableBuilder
类只需要进行测试,以确保它调用
create()
方法,无论它从
LoadbableBuilder得到什么,都有正确的参数。你知道我可以使用更好的工厂模式吗,它更易于单元测试,可以更有效地将它与构建器分离?我可以更改它,因为API现在正在积极开发中。我不确定它的rth支持解耦,但您可以创建一个服务,提供一个
ILoadableFactory
,您可以在将来轻松更改该服务,以根据配置提供各种类型的工厂。但您仍然必须遵循与此答案类似的模式来进行单元测试。因此,我遵循了您的建议,为您编写了一个接口我的工厂,我的具体类现在实现了它。让我的构建器使用受保护的方法访问工厂,我有点不高兴。但是,我喜欢可测试性。所以,我使用你的解决方案!谢谢。另一种方法允许你在检索构建器时有更多的自由,但我可以理解它如何工作的不安rks。如果你考虑扩展这个类,你现在可以在你的子类中得到一个不同的工厂,但是其他的一切都是相同的。我也意识到了这一点,在我考虑之后。我决定这对任何扩展构建器的人都是好的,构建器打算扩展到所有类型的组件。谢谢你的建议。