我试图监视一个对象,我想在构造函数调用它之前存根构造函数调用的方法。
我的类是这样的:
public class MyClass {
public MyClass() {
setup();
}
public void setup() {
}
}
不能调用setup方法。那么,我如何监视这个方法(和存根设置,使它什么都不做)?
它可以很好地模拟方法,但我想对MyClass
进行单元测试,所以我需要非常其他的方法。
需要存根setup方法以使其不做任何事情的原因:
我正在编写一个乐高机器人(lejos),我在机器人需要工作的设置中放入了一些代码。但是,当我在TinyVM(安装在机器人上的虚拟机)之外调用它时,由于虚拟机没有正确初始化(因为测试在我的PC上运行),java崩溃了。对于单元测试,设置并不重要。
我不能存根类/方法设置调用,因为其中一些是公共静态final变量。
直接回答你的问题,你不能使用Mockito来存根从构造函数调用的方法。Mockito在开始mock之前需要一个类的实例,而您还没有为自己创建一个用于测试的实例。
更一般地说,如Effective Java第17项所述,您不应该从构造函数调用可重写的方法。如果您这样做,例如,您可以在子类中提供一个覆盖,该子类引用final
字段,但在final
字段设置之前运行。这可能不会给你带来麻烦,但在Java中这是一个坏习惯。
幸运的是,你可以重构你的代码来很容易地做到这一点:
public class MyClass {
public MyClass() {
this(true);
}
/** For testing. */
MyClass(boolean runSetup) {
if (runSetup) {
setup();
}
}
/* ... */
}
为了使它更明显,您可以将一个参数MyClass
构造函数设为私有,并提供一个public static
工厂方法:
/* ... */
public static MyClass createForTesting() {
return new MyClass(false);
}
private MyClass(boolean runSetup) {
/* ... */
尽管一些开发人员认为用主要用于测试的方法编写代码是一种不好的做法,但请记住,您负责代码的设计,测试是您绝对知道需要适应的少数消费者之一。虽然在"生产"代码中避免显式的测试设置仍然是一个好主意,但是为了测试而创建额外的方法或重载通常会使您的代码总体上更干净,并且可以大大提高您的测试覆盖率和可读性。
谢谢你的建议,但是有点太复杂了。
最后,我通过扩展类和覆盖我的setup方法来模拟这个方法。这样默认构造函数就不会调用它的setup实现,而是调用被覆盖的方法。
下面是代码:
// src/author/MyClass.java
public class MyClass {
public MyClass() {
setup();
}
protected void setup() {
throw new Exception("I hate unit testing !");
}
public boolean doesItWork() {
return true;
}
}
// test/author/MyClass.java
public class MyClassTest {
private class MockedMyClass extends MyClass {
@Override
protected void setup() {
}
}
private MyClass instance;
@Before
public void setUp() { // Not to be confusing with `MyClass#setup()`!
instance = new MockedMyClass();
}
@Test
public void test_doesItWork() {
assertTrue(instance.doesItWork());
}
}
如果你不希望MyTest的setup方法被其他子类调用或覆盖,除了你的测试(因为其他开发人员可能会通过使用setup方法把事情搞得很糟),只需将可见性更改为默认,只有你的类才能调用setup。
如果有更简单的方法,请回答这个问题,因为我对我的解决方案不是100%满意。
使用PowerMock。
导入库后,将其设置为使用不能调用的实例方法操作要模拟的类。
一样:
@RunWith(PowerMockRunner.class)
@PrepareForTest({<Other classes>, Myclass.class})
- 在测试开始时禁用该方法。
- 在测试中根据需要定制setup()方法的行为。
一样:
suppress(method(Myclass.class, "setup"));
一样:
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
// code here
return null;
}
}).when(Myclass.class, "setup");
@fragorl的答案将对静态类方法工作,但对实例方法失败。如果您需要模拟/监视实例方法,而不是抑制replace:
PowerMockito.replace(MemberMatcher.method(Myclass.class, "setup")).with(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] arguments) throws Throwable {
//add some special behaviour
//if needed can call original method afterwards in that way:
//method.invoke(o, arguments);
return null;
}
});