Mockito:验证使用函数参数调用了一个方法

8

我有一个简单的场景,想要验证某个方法被调用时的行为(即在这种情况下使用给定参数、函数指针调用了特定的方法)。以下是我的类:

@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$8/438123546@282308c3
);
-> at com.spring.starter.AppBootStrapperTest.start(AppBootStrapperTest.java:24)
Actual invocation has different arguments:
networkScanner bean.addConsumer(
    com.spring.starter.AppBootStrapper$$Lambda$7/920446957@5dda14d0
);
-> at com.spring.starter.AppBootStrapper.start(AppBootStrapper.java:12)

从异常情况来看,函数指针并不相同。

我这样做是否正确?我是否忽略了一些基本的内容?我尝试了将一个消费者注入到PacketConsumer中,以查看是否有所改变,但是我知道那肯定不是正确的方法。

如果您能提供任何帮助或观点,将不胜感激。

1个回答

7

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。即使方法体相同,并且从同一方法引用以相同的方式构建。

如果您在测试中显式生成Consumer并将其保存为static final Consumer<String>字段,则可以在测试中传递该引用并进行比较;此时,引用相等性应该成立。这对于lambda表达式或方法引用来说都可以正常工作。

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

请参见此SO问题上的评论和答案。


感谢@Jeff Bowman。解释得非常好。我喜欢在消费者层面进行验证的想法。 - Aliyu Fonyuy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接