为什么Powermock PrepareForTest可以防止模拟注入?

时间:2019-03-04 11:24:38

标签: powermock

我的代码如下:

public class RealWorldBoImpl extends AbstractBoImpl<T> implements SomeBo{}

@RunWith(PowerMockRunner.class)
@PrepareForTest({RealWorldBoImpl.class})
public class RealWorldBoImplTest {
    @InjectMocks
    private RealWorldBoImpl realWorldBo;
    @Mock
    private RealWorldDAO realWorldDAO;

    @Test
    public void changeStatusMainSubString() throws Exception {
        long id = 1L;
    }

在这种情况下,realWorldDAO无法注入realWorldBo。但是当我删除PrepareForTest时,它可以工作。

我也尝试了其他课程,它们效果很好。似乎RealWorldBoImpl的特殊之处在于,在进行准备时,它将无法正确注入模拟。

我调试了这段代码,发现在org.mockito.internal.util.reflection.FieldInitializer#checkParameterized中,constructor.getParameterTypes()不为空,并且具有带有IndicateReloadClass类的构造函数。

private void checkParameterized(Constructor<?> constructor, Field field) {
        if(constructor.getParameterTypes().length == 0) {
            throw new MockitoException("the field " + field.getName() + " of type " + field.getType() + " has no parameterized constructor");
        }
    }

但是我不知道RealWorldBoImpl有什么特别之处。它只是扩展了父类并实现了一个接口。有关系吗?

1 个答案:

答案 0 :(得分:0)

当PowerMock准备一个具有除Object以外的超类的类时,它将向该类添加一个构造函数,该构造函数接受类型为org.powermock.core.IndicateReloadClass的参数。

为什么PowerMock会这样做? PowerMock通过此机制实现超类构造函数抑制。 在您的情况下,因为RealWorldBoImpl源自AbstractBoImpl,PowerMock将以下构造函数添加到RealWorldBoImplAbstractBoImpl类中:

  public RealWorldBoImpl(IndicateReloadClass var1) {
    super(var1);
  }

  public AbstractBoImpl(IndicateReloadClass var1) {
    super(); //assuming the parent class is Object otherwise super(var1)
  }

并将RealWorldBoImpl的默认no-arg构造函数更改为以下内容:

  public RealWorldBoImpl() {
    Object var1 = MockGateway.constructorCall(Desc.getClazz("org.example.RealWorldBoImpl"),
        new Object[0], Desc.getParams("()V"));
    if (var1 != MockGateway.PROCEED) {
      super((IndicateReloadClass)null);
    } else {
      super();
    }
  }

那是PowerMock的一部分,现在让我们进入Mockito的部分。

Mockito有两种注入策略(MockInjectionStrategy):ConstructorInjectionPropertyAndSetterInjection

ConstructorInjection使用构造函数注入模拟,并在被注入者具有至少一个非默认构造函数(至少接受一个参数的构造函数)时使用。如果被注入者只有一个无参数构造函数,则Mockito使用PropertyAndSetterInjection来使用setter方法,如果没有setter方法,则通过反射设置字段的值来直接注入模拟。

在您的情况下,当您准备RealWorldBoImpl类时,您有一个带有一个参数的构造函数,而Mockito使用ConstructorInjection通过PowerMock添加的构造函数向对象注入模拟对象(并且没有模拟对象)类型为IndicateReloadClass的Mockito将null传递给构造函数,但这无关紧要,因为构造函数什么也不做),因此不会注入任何模拟。

那么您如何解决问题?如果要注入多个模拟,请向注入对象类中添加一个构造函数,该构造函数的参数与要注入的模拟数量一样多。只要您的构造函数具有多个参数,注入就可以工作,否则您的被注入者类应具有Object作为其超类。

如果仅要注入一个模拟,则可以向构造函数添加一个虚拟参数,以使Mockito选择由PowerMock添加的构造函数:

public RealWorldBoImpl(RealWorldDAO realWorldDAO, String dummy) {
    this.realWorldDAO = realWorldDAO;
}