Mockito:使用函数参数调用验证方法
我有一个简单的场景,其中尝试验证某个方法被调用时的某些行为(即某个方法是使用给定参数调用的,此场景中的函数指针)。下面是我的课:Mockito:使用函数参数调用验证方法
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
AppBootStrapper bootStrapper = context.getBean(AppBootStrapper.class);
bootStrapper.start();
}
}
@Component
public class AppBootStrapper {
private NetworkScanner networkScanner;
private PacketConsumer packetConsumer;
public AppBootStrapper(NetworkScanner networkScanner, PacketConsumer packetConsumer) {
this.networkScanner = networkScanner;
this.packetConsumer = packetConsumer;
}
public void start() {
networkScanner.addConsumer(packetConsumer::consumePacket);
networkScanner.startScan();
}
}
@Component
public class NetworkScanner {
private List<Consumer<String>> consumers = new ArrayList<>();
public void startScan(){
Executors.newSingleThreadExecutor().submit(() -> {
while(true) {
// do some scanning and get/parse packets
consumers.forEach(consumer -> consumer.accept("Package Data"));
}
});
}
public void addConsumer(Consumer<String> consumer) {
this.consumers.add(consumer);
}
}
@Component
public class PacketConsumer {
public void consumePacket(String packet) {
System.out.println("Packet received: " + packet);
}
}
@RunWith(JUnit4.class)
public class AppBootStrapperTest {
@Test
public void start() throws Exception {
NetworkScanner networkScanner = mock(NetworkScanner.class);
PacketConsumer packetConsumer = mock(PacketConsumer.class);
AppBootStrapper appBootStrapper = new AppBootStrapper(networkScanner, packetConsumer);
appBootStrapper.start();
verify(networkScanner).addConsumer(packetConsumer::consumePacket);
verify(networkScanner, times(1)).startScan();
}
}
我想验证引导程序并事实上通过注册数据包的消费者做正确的设置(有可能是后来注册的其他用户,但是这一次是强制性的),然后叫startScan。当我执行测试用例时,我收到以下错误信息:
Argument(s) are different! Wanted:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapperTest$$Lambda$8/[email protected]
);
-> at com.spring.starter.AppBootStrapperTest.start(AppBootStrapperTest.java:24)
Actual invocation has different arguments:
networkScanner bean.addConsumer(
com.spring.starter.AppBootStrapper$$Lambda$7/[email protected]
);
-> at com.spring.starter.AppBootStrapper.start(AppBootStrapper.java:12)
从例外情况来看,显然函数指针并不相同。
我接近这个正确的方式吗?有什么基本的我失踪?我四处游玩,让一位消费者注入PacketConsumer,看看它是否与众不同,但这是可以的,但我知道这当然不是正确的选择。
任何帮助,对此的观点将不胜感激。
Java没有任何“函数指针”的概念;当你看到:
networkScanner.addConsumer(packetConsumer::consumePacket);
什么的Java编译实际上是(相当于):
networkScanner.addConsumer(new Consumer<String>() {
@Override void accept(String packet) {
packetConsumer.consumePacket(packet);
}
});
这个匿名内部类碰巧被称为AppBootStrapper$$Lambda$7
。因为它没有(也不应该)定义一个equals
方法,所以它永远不会等于编译器在测试中生成的匿名内部类,它恰好被称为AppBootStrapperTest$$Lambda$8
。这与方法体是相同的事实无关,并且以相同方法参考的相同方式构建。
如果您在测试中明确生成消费者并将其保存为static final Consumer<String>
字段,那么您可以在测试中传递该参考并进行比较;在这一点上,参考平等应该成立。这应该适用于lambda表达式或方法引用。
更适合的测试可能会verify(packetConsumer, atLeastOnce()).consumePacket(...)
,因为lambda的内容是一个实现细节,你真的更关心你的组件如何与其他组件协作。此处的抽象级别应该在consumePacket
级别,而不是addConsumer
级别。
请参阅this SO question上的评论和回答。
谢谢@Jeff Bowman。很好地解释。我喜欢在消费者层面进行验证的想法。 –