2013-06-10 7 views
6

Natrafiliśmy na naprawdę nieprzyjemny problem z Mockito.Mockito: co się stanie, jeśli zmieniony zostanie argument przekazywany na próbę?

Kod:

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

powoduje Ten komunikat o błędzie jak:

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

co się stało:

Mockito rekordy referencyjnych do list zamiast kopii list, więc w powyższym kodzie Mockito weryfikuje zamiast zmodyfikowaną wersję (pusta lista, []) zamiast tego, który faktycznie przeszedł podczas wywoływania ([1,2,3])!

Pytanie:

Czy istnieje elegancki i czyste rozwiązanie tego problemu inne niż robi kopię obronną jak poniżej (które rzeczywiście pomaga, ale nie lubię tego rozwiązania)?

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

Nie chcemy zmodyfikować prawidłowy kod produkcyjną i zmniejszyć jego wydajność tylko rozwiązać problem techniczny z testu.

Zadaję to pytanie tutaj, ponieważ wydaje się, że jest to prawdopodobnie typowy problem z Mockito. Lub po prostu robimy coś nie tak?

PS. To nie jest prawdziwy kod, więc nie pytaj, dlaczego tworzymy listę, a następnie usuwamy ją itp. W prawdziwym kodzie mamy prawdziwą potrzebę zrobienia czegoś podobnego :-).

+0

Czy rzeczywiście weryfikujesz na _różnej instancji_ tej samej klasy argumentów? – fge

+0

@ fge Tak, nie mam dostępu do oryginalnej instancji w kodzie testowym, więc muszę utworzyć nową instancję argumentu w teście z oczekiwaną zawartością. –

+0

Następnie zobacz moją odpowiedź – fge

Odpowiedz

11

Rozwiązaniem jest tutaj użycie niestandardowej odpowiedzi. Dwie próbki kodu: pierwsza to używane klasy testowe, druga to test.

pierwszy zajęcia testowe:

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

On do testu:

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

Oczywiście, będąc w stanie napisać taki test wymaga, że ​​jesteś w stanie wprowadzić do swojej „zewnętrzny obiekt "stan wystarczający, aby test był znaczący ... Tutaj jest stosunkowo łatwo.

+0

Dziękuję za odpowiedź, ale jestem całkiem pewien, że domyślnie 'eq' matcher jest używany, a nie' ten sam ". Zaktualizowałem moje pytanie, aby je wyjaśnić. Jak widzisz, kiedy umieszczę kopię "listy", test przejdzie (to znaczy, że użyty zostanie 'eq' matcher). Ale to rozwiązanie nas nie zadowala. –

+0

Ah OK. Potem mam inne rozwiązanie! – fge

+0

Zobacz moją odpowiedź. Testowane i działa! – fge