Mockito: Verifying a method was called with a functional parameter

最后都变了- 提交于 2021-02-07 12:22:17

问题


I have a simple scenario in which am trying to verify some behavior when a method is called (i.e. that a certain method was called with given parameter, a function pointer in this scenario). Below are my classes:

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

I want to verify that bootStrapper did in fact do proper setup by registering the packet consumer(there might be other consumers registered later on, but this one is mandatory) and then called startScan. I get the following error message when I execute the test case:

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)

From the exception, clearly the function pointers aren't the same.

Am I approaching this the right way? Is there something basic I am missing? I played around and had a consumer injected into PacketConsumer just to see if it made a different and that was OK, but I know that's certainly not the right way to go.

Any help, perspectives on this would be greatly appreciated.


回答1:


Java doesn't have any concept of "function pointers"; when you see:

networkScanner.addConsumer(packetConsumer::consumePacket);

What Java actually compiles is (the equivalent of):

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

This anonymous inner class happens to be called AppBootStrapper$$Lambda$7. Because it doesn't (and shouldn't) define an equals method, it will never be equal to the anonymous inner class that the compiler generates in your test, which happens to be called AppBootStrapperTest$$Lambda$8. This is regardless of the fact that the method bodies are the same, and are built in the same way from the same method reference.

If you generate the Consumer explicitly in your test and save it as a static final Consumer<String> field, then you can pass that reference in the test and compare it; at that point, reference equality should hold. This should work with a lambda expression or method reference just fine.

A more apt test would probably verify(packetConsumer, atLeastOnce()).consumePacket(...), as the contents of the lambda are an implementation detail and you're really more concerned about how your component collaborates with other components. The abstraction here should be at the consumePacket level, not at the addConsumer level.

See the comments and answer on this SO question.



来源:https://stackoverflow.com/questions/38960539/mockito-verifying-a-method-was-called-with-a-functional-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!