2013-06-10 3 views
6

Мы столкнулись с действительно неприятной проблемой с Мокито.Mockito: что, если аргумент, переданный макету, изменен?

Код:

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

Это вызывает сообщение об ошибке, как:

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

Что только что произошло:

Mockito записи эталонные к list, а не копию list, поэтому в коде выше Mockito проверяет измененную версию (пустой список, []) вместо того, который фактически прошел во время вызова ([1,2,3])!

Вопрос:

Есть ли элегантное и чистое решение этой проблемы, кроме делает защитную копию, как показано ниже (который на самом деле помогает, но нам не нравится это решение)?

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

Мы не хотим, чтобы изменить правильный код производства и снижают его производительность только для устранения технических проблем с тестом.

Я задаю этот вопрос здесь, потому что это, возможно, общая проблема с Mockito. Или мы просто делаем что-то не так?

PS. Это не настоящий код, поэтому, пожалуйста, не спрашивайте, почему мы создаем список, а затем очищаем его и т. Д. В реальном коде нам действительно нужно сделать что-то подобное :-).

+0

Действительно ли вы проверяете экземпляр _different экземпляра того же класса аргументов? – fge

+0

@fge Да, у меня нет доступа к исходному экземпляру в тестовом коде, поэтому мне нужно создать новый экземпляр аргумента в тесте с ожидаемым контентом. –

+0

Тогда см. Мой ответ – fge

ответ

11

Решение здесь - использовать индивидуальный ответ. Два примера кода: первый - используемые классы тестов, второй - тест.

Во-первых, тестовые классы:

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

На тесту:

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

Конечно, будучи в состоянии написать такой тест требует, чтобы вы смогли привнести в ваш «внешней объект "достаточно, чтобы тест был значимым ... Здесь это относительно легко.

+0

Спасибо за ответ, но я уверен, что по умолчанию используется 'eq' matcher, а не' same'. Я уточнил свой вопрос, чтобы прояснить это. Как вы видите, когда я помещаю копию «списка», тест пройдет (это означает, что используется 'eq' matcher). Но это решение нас не удовлетворяет. –

+0

Ах ОК. Тогда у меня есть другое решение! – fge

+0

См. Мой ответ. Протестировано и работает! – fge