Mockito:验证使用功能参数调用的方法

Mockito: Verifying a method was called with a functional parameter

我有一个简单的场景,在这个场景中我试图在调用一个方法时验证某些行为(即某个方法是用给定参数调用的,在这个场景中是一个函数指针)。下面是我的 类:

@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();
    }
}

我想验证 bootStrapper 是否确实通过注册数据包消费者(稍后可能有其他消费者注册,但这是强制性的)并调用 startScan 来正确设置。执行测试用例时出现以下错误消息:

Argument(s) are different! Wanted:
networkScanner bean.addConsumer(
    com.spring.starter.AppBootStrapperTest$$Lambda/438123546@282308c3
);
-> at com.spring.starter.AppBootStrapperTest.start(AppBootStrapperTest.java:24)
Actual invocation has different arguments:
networkScanner bean.addConsumer(
    com.spring.starter.AppBootStrapper$$Lambda/920446957@5dda14d0
);
-> at com.spring.starter.AppBootStrapper.start(AppBootStrapper.java:12)

从异常来看,显然函数指针不一样。

我的处理方式是否正确?我缺少一些基本的东西吗?我试了一下,将一个消费者注入到 PacketConsumer 中,只是为了看看它是否有所不同,这没问题,但我知道这肯定不是正确的方法。

任何帮助,对此的看法将不胜感激。

Java没有"function pointers"的概念;当你看到:

networkScanner.addConsumer(packetConsumer::consumePacket);

Java实际编译的是(相当于):

networkScanner.addConsumer(new Consumer<String>() {
  @Override void accept(String packet) {
    packetConsumer.consumePacket(packet);
  }
});

这个匿名内部 class 恰好被称为 AppBootStrapper$$Lambda。因为它没有(也不应该)定义 equals 方法,所以它永远不会等于编译器在您的测试中生成的匿名内部 class,它恰好被称为 AppBootStrapperTest$$Lambda.这与方法主体相同并且是从相同的方法引用以相同的方式构建的事实无关。

如果您在测试中显式生成 Consumer 并将其保存为 static final Consumer<String> 字段,那么您可以在测试中传递该引用并进行比较;在这一点上,参考平等应该成立。这应该可以很好地与 lambda 表达式或方法引用一起使用。

更合适的测试可能是 verify(packetConsumer, atLeastOnce()).consumePacket(...),因为 lambda 的内容是一个实现细节,您实际上更关心您的组件如何与其他组件协作。这里的抽象应该是consumePacket层级,而不是addConsumer层级。

查看 上的评论和回答。