2013-06-10 4 views
6

Wir stießen auf wirklich unangenehme Probleme mit Mockito.Mockito: Was passiert, wenn das Argument zum Mock verändert wird?

Code:

public class Baz{ 
    private Foo foo; 
    private List list; 

    public Baz(Foo foo){ 
     this.foo = foo; 
    } 

    public void invokeBar(){ 
     list = Arrays.asList(1,2,3); 
     foo.bar(list); 
     list.clear(); 
    } 

} 


public class BazTest{ 

    @Test 
    void testBarIsInvoked(){ 
     Foo mockFoo = mock(Foo.class); 
     Baz baz = new Baz(mockFoo);   
     baz.invokeBar(); 
     verify(mockFoo).bar(Arrays.asList(1,2,3)); 
    } 
} 

Dies verursacht Fehlermeldung wie:

Arguments are different! Wanted: 
foo.bar([1,2,3]); 
Actual invocation has different arguments: 
foo.bar([]); 

Was gerade passiert ist:

Mockito Aufzeichnungen Referenz-list eher als Kopie list, also im obigen Code verifiziert Mockito gegen geänderte Version (leere Liste, []) statt zu der, die tatsächlich während des Aufrufs übergeben wurde ([1,2,3])!

Frage:

Gibt es eine elegante und saubere Lösung für dieses Problem anders als wie unten eine defensive Kopie zu tun (was tatsächlich hilft, aber wir haben nicht diese Lösung mögen)?

public void fun(){ 
     list = Arrays.asList(1,2,3); 
     foo.bar(new ArrayList(list)); 
     list.clear(); 
    } 

Wir wollen nicht richtigen Produktionscode zu modifizieren und seine Leistung reduzieren nur technisches Problem mit Test zu beheben.

Ich stelle diese Frage hier, weil es ein mögliches allgemeines Problem mit Mockito zu sein scheint. Oder wir machen nur etwas falsch?

PS. Das ist kein richtiger Code, also frag bitte nicht, warum wir eine Liste erstellen und dann löschen usw. In echtem Code haben wir ein echtes Bedürfnis, etwas ähnliches zu tun :-).

+0

Verifizieren Sie tatsächlich eine _different-Instanz_ derselben Argumentklasse? – fge

+0

@fge Ja, ich habe keinen Zugriff auf die ursprüngliche Instanz im Testcode, daher muss ich eine neue Instanz des Arguments im Test mit dem erwarteten Inhalt erstellen. –

+0

Dann sehe meine Antwort – fge

Antwort

11

Die Lösung hier ist eine benutzerdefinierte Antwort verwenden. Zwei Codebeispiele: Das erste ist die Testklasse, das zweite ist der Test.

Zunächst werden die Testklassen:

private interface Foo 
{ 
    void bar(final List<String> list); 
} 

private static final class X 
{ 
    private final Foo foo; 

    X(final Foo foo) 
    { 
     this.foo = foo; 
    } 

    void invokeBar() 
    { 
     // Note: using Guava's Lists here 
     final List<String> list = Lists.newArrayList("a", "b", "c"); 
     foo.bar(list); 
     list.clear(); 
    } 
} 

Auf zum Test:

@Test 
@SuppressWarnings("unchecked") 
public void fooBarIsInvoked() 
{ 
    final Foo foo = mock(Foo.class); 
    final X x = new X(foo); 

    // This is to capture the arguments with which foo is invoked 
    // FINAL IS NECESSARY: non final method variables cannot serve 
    // in inner anonymous classes 
    final List<String> captured = new ArrayList<String>(); 

    // Tell that when foo.bar() is invoked with any list, we want to swallow its 
    // list elements into the "captured" list 
    doAnswer(new Answer() 
    { 
     @Override 
     public Object answer(final InvocationOnMock invocation) 
      throws Throwable 
     { 
      final List<String> list 
       = (List<String>) invocation.getArguments()[0]; 
      captured.addAll(list); 
      return null; 
     } 
    }).when(foo).bar(anyList()); 

    // Invoke... 
    x.invokeBar(); 

    // Test invocation... 
    verify(foo).bar(anyList()); 

    // Test arguments: works! 
    assertEquals(captured, Arrays.asList("a", "b", "c")); 
} 

Natürlich solch einen Test schreiben zu können, setzt voraus, dass Sie in der Lage sind, in die „Außen zu injizieren Objekt "ausreichender Zustand, damit der Test sinnvoll ist ... Hier ist es relativ einfach.

+0

Danke für die Antwort, aber ich bin mir ziemlich sicher, dass standardmäßig 'eq' Matcher verwendet wird, nicht' same'. Ich habe meine Frage aktualisiert, um sie zu klären. Wie Sie sehen, wenn ich eine Kopie von "liste" lege, wird der Test bestanden (dh "eq" -Matcher wird verwendet). Aber diese Lösung befriedigt uns nicht. –

+0

Ah OK. Dann habe ich eine andere Lösung! – fge

+0

Siehe meine Antwort. Getestet und es funktioniert! – fge