2013-01-17 111 views
7

鑑於以下代碼:的Mockito - 懲戒具體類

LinkedList list = mock(LinkedList.class); 
    doCallRealMethod().when(list).clear(); 
    list.clear(); 

通過執行該試驗中,一個NullPointerException是從鏈表#第一線清晰拋出:

public void clear() { 
    Entry<E> e = header.next; 
    while (e != header) { 
     Entry<E> next = e.next; 
     //Code omitted. 

一直實例化之前:

private transient Entry<E> header = new Entry<E>(null, null, null); 

莫非一些請解釋模擬創作過程中發生了什麼?

#######更新。 ######

閱讀所有答案,特別是Ajay的答案後,我查看了Objenesis源代碼,並發現它使用Reflection API創建代理實例(通過CGLIB),因此繞過層次結構中的所有構造函數直到java.lang.Object。

下面是示例代碼模擬了問題:

public class ReflectionConstructorTest { 

    @Test 
    public void testAgain() { 

     try { 
      //java.lang.Object default constructor 
      Constructor javaLangObjectConstructor = Object.class 
        .getConstructor((Class[]) null); 
      Constructor mungedConstructor = ReflectionFactory 
        .getReflectionFactory() 
        .newConstructorForSerialization(CustomClient.class, javaLangObjectConstructor); 

      mungedConstructor.setAccessible(true); 

      //Creates new client instance without calling its constructor 
      //Thus "name" is not initialized. 
      Object client = mungedConstructor.newInstance((Object[]) null); 

      //this will print "CustomClient" 
      System.out.println(client.getClass()); 
      //this will print "CustomClient: null". name is null. 
      System.out.println(client.toString()); 

     } catch(Exception e) { 
      e.printStackTrace(); 
     } 
    } 
} 


class CustomClient { 
    private String name; 

    CustomClient() { 
     System.out.println(this.getClass().getSimpleName() + " - Constructor"); 
     this.name = "My Name"; 
    } 

    @Override 
    public String toString() { 
     return this.getClass().getSimpleName() + ": " + name; 
    } 
} 
+0

我們展示了全堆棧跟蹤 – Archer

回答

5

你的推理是完美無瑕的。
關鍵的問題是,你是不是實際LinkedList對象上運行。以下是幕後發生的事情:

Mockito的mock()給出的對象是來自CGLIB庫的Enhancer對象。

對我來說是一樣的東西java.util.LinkedList$$EnhancerByMockitoWithCGLIB$$cae81a28

這有點像代理行爲,儘管有設置爲默認值的字段。 (NULL,0等)

+0

你是對的,但是這個例外是從真實對象中拋出的。即使通過調試我可以達到真正的方法。我的問題是,如果場景背後有一個真正的「LinkedList」對象,那麼它的內部狀態應該也可以。 – mhshams

+0

您能夠使用LinkedList引用來引用模擬對象的原因意味着模擬對象是LinkedList的子類。希望有所幫助。 –

+0

即使模擬對象是Linkedlist的子類,爲了創建這個子類對象,您需要創建父類對象。 (應該調用父構造函數) – mhshams

7

你只是要求給的Mockito呼籲清晰真實的東西,根本目的仍然是爲的Mockito您創建一個假的。如果你需要一個真正的LinkedList,那麼就使用LinkedList - 只有BDD最激烈的純粹主義者會告訴你嘲笑你周圍的一切。我的意思是,你不是在嘲笑你是弦嗎?

創作的Mockito自己也說過,調用真實的東西應該很少使用,通常只用於測試遺留代碼。

如果你需要刺探真實物體上(跟蹤調用),則具有的Mockito這個功能太:

List list = new LinkedList(); 
List spy = spy(list); 

隨着間諜,你仍然可以存根的方法,如果你需要。當你嘲笑一個類,你正在使用的對象是假的,所以變量沒有被實例化,並如期方法不起作用)

+1

這是事實,但不回答這個問題。事實上,這樣做不是一個好主意,但我們仍然可以找出異常發生的原因,不是嗎?弗拉德給出了他的解釋,+1。 – Fildor

+0

因爲我很傷心:你只要求Mockito清楚地調用真實的東西,對象本身沒有實例化,mockito在它下面爲你創建一個假的。我已經修改了答案,使其更加清晰。下一次我會嘗試更加明確。 – theadam

+1

沒有犯法:)只是想指出,有時,有答案不回答問題,但質疑OP的行動過程。而這些有時會因爲這樣做而被低估。我發現這是一個很好的做法,首先告訴OP爲什麼它不在做他所做的事情(從而回答這個問題),然後提出一個替代方案,如果我有一個提供方案。編輯完成後,您也可以+1。 – Fildor

1

;基本上,它就像一個模擬的,但並非如此。你可以使用反射設置標題的值,但我真的不會推薦這個。正如阿達姆所說,最好的做法是僅僅使用一個列表。