我想测试以下类Foo
的构造函数。
import java.util.List;
import java.util.stream.Collectors;
public class Foo {
public List<Bar> bars;
public Foo(List<Integer> list) {
bars = list.stream()
.map(Bar::new)
.collect(Collectors.toList());
}
}
其中Bar
看起来像这样:
public class Bar {
public Bar(Integer baz) {
throw new RuntimeException("This crashes, but who cares, I’m testing Foo!");
}
}
由于我对Bar
的实施充满信心,我想通过嘲笑Foo
来测试我对Bar::new
的实现。
这是我尝试过的:
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
@RunWith(PowerMockRunner.class)
@PrepareForTest(Foo.class) // where Bar::new is referenced
public class FooTest {
@Test
public void testFoo () throws Exception {
Bar firstBar = PowerMockito.mock(Bar.class);
Bar secondBar = PowerMockito.mock(Bar.class);
PowerMockito.whenNew(Bar.class).withArguments(1)
.thenReturn(firstBar);
PowerMockito.whenNew(Bar.class).withArguments(2)
.thenReturn(secondBar);
Foo actual = new Foo(Arrays.asList(1, 2));
Assert.assertNotNull(actual);
Assert.assertNotNull(actual.bars);
Assert.assertEquals(firstBar, actual.bars.get(0));
Assert.assertEquals(secondBar, actual.bars.get(1));
}
}
在这种情况下,调用实际的Bar::new
,并且引发RunTimeException
。回想起来这是有道理的,因为实际调用new不是在Foo
(它会被模拟)中,而是在一些与流相关的匿名类中(我认为)。
如何在不依赖Foo
构造函数的实际实现的情况下实现测试Bar
的原始目标?
好的,我已经发现这种情况正在发生。表达式Bar::new
是一个lambda,PowerMock无法模拟。如果你将它重写为i -> new Bar(i)
,即使用显式的“new Bar”运算符 - 它可以正常工作。
因此,重点是使用POWERMOCK - 这很糟糕!看到我的另一个答案。
使用一个简单的重构来提取Bar工厂,你不需要做任何与powermock hacks有关的事情。
class Foo {
public List<Bar> bars;
// existing constructor
public Foo(List<Integer> list) {
this(list, Bar::new);
}
// constructor which trivial to test.
public Foo(List<Integer> list, Function<Integer, Bar> barFactory) {
bars = list.stream()
.map(barFactory)
.collect(Collectors.toList());
}
}