【问题标题】:Mockito: Verifying a method was called with a functional parameterMockito:验证方法是使用功能参数调用的
【发布时间】:2016-08-15 18:10:46
【问题描述】:

我有一个简单的场景,我试图验证调用方法时的某些行为(即使用给定参数调用某个方法,在这种情况下为函数指针)。以下是我的课程:

@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 中,只是为了看看它是否有所不同,这没关系,但我知道这肯定不是正确的方法。

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

【问题讨论】:

    标签: java-8 mockito junit4


    【解决方案1】:

    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&lt;String&gt; 字段,那么您可以在测试中传递该引用并进行比较;在这一点上,引用平等应该成立。这应该适用于 lambda 表达式或方法引用。

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

    this SO question上查看cmets并回答。

    【讨论】:

    • 感谢@Jeff Bowman。大大解释了。我喜欢在消费者层面进行验证的想法。
    猜你喜欢
    • 2017-02-18
    • 2021-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多