2016-08-15 169 views
4

我有一個簡單的場景,其中嘗試驗證某個方法被調用時的某些行爲(即某個方法是使用給定參數調用的,此場景中的函數指針)。下面是我的課: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,看看它是否與衆不同,但這是可以的,但我知道這當然不是正確的選擇。

任何幫助,對此的觀點將不勝感激。

回答

5

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上的評論和回答。

+0

謝謝@Jeff Bowman。很好地解釋。我喜歡在消費者層面進行驗證的想法。 –

相關問題