среда, 1 августа 2012 г.

testing: mockito

Mockito.mock(...) позволяет динамически создать экземпляр, с классом, который является подклассом переданного класса. Если мы сделаем
    System.out.println(listMock.getClass());
то увидим нечто вроде
    $java.util.List$$EnhancerByMockitoWithCGLIB$$29cb8e61
import org.mockito.Mockito;
import java.util.List;

public class MockitoDemo0 {
    public static void main(String[] args) {
        // arrange
        List<String> listMock = Mockito.mock(List.class);
        // act
        System.out.println(listMock.getClass());
    }
}
    В качестве аргумента метода mock() можно передавать как классы, так и интерфейсы.
    Можно увидеть прямую аналогию с java.lang.reflet.Proxy:
List list = Proxy.newProxyInstance(..., new Class[]{List.class}, handler);
Но в данном случае мы не передаем handler. 



Каково же поведение полученного mock?
import org.mockito.Mockito;
import java.util.List;

public class MockitoDemo1 {

    public static void main(String[] args) {
        // arrange
        List<String> listMock = Mockito.mock(List.class);
        // act
        System.out.println(listMock.get(0));
    }
}
Данная программа выведет 
>> null
Stubbed методы по умолчанию возвращают null, 0, false в зависимости от типа.

Одна из удивительных способностей Mockito состоит в том, что можно "красиво" определять, что будет возвращать stubbed метод:
import org.mockito.Mockito;
import java.util.List;

public class MockitoDemo2 {
    public static void main(String[] args) {
        // arrange
        List<String> listMock = Mockito.mock(List.class);
        Mockito.when(listMock.get(0)).thenReturn("stub");
        // act
        System.out.println(listMock.get(0)); // pring "stub"
    }
}

Данная программа выведет 
>> "stub"


Но, если мы попробуем вызвать для "незнакомого" аргумента, то получим значение по умолчанию.
import org.mockito.Mockito;
import java.util.List;

public class MockitoDemo3 {

    public static void main(String[] args) {
        // arrange
        List<String> listMock = Mockito.mock(List.class);
        Mockito.when(listMock.get(0)).thenReturn("stub");
        // act
        System.out.println(listMock.get(0));
        System.out.println(listMock.get(1));
    }
}

Данная программа выведет 
>> "stub"
>> null


Вы можем переопределить значение по умолчанию
import org.mockito.Mockito;
import java.util.List;

public class MockitoDemo4 {

    public static void main(String[] args) {
        // arrange
        List<String> listMock = Mockito.mock(List.class);
        Mockito.when(listMock.get(Mockito.anyInt())).thenReturn("stub");
        // act
        System.out.println(listMock.get(0));
        System.out.println(listMock.get(1));
    }
}
Данная программа выведет 
>> "stub"
>> "stub"

Демонстрация установки более сложного поведения:
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import java.util.List;

public class MockitoDemo5 {

    public static void main(String[] args) {
        // arrange
        List<String> listMock = Mockito.mock(List.class);
        Mockito.when(listMock.get(Mockito.anyInt())).thenReturn("odd");
        Mockito.when(listMock.get(evenInt())).thenReturn("even");
        // act
        System.out.println(listMock.get(0));
        System.out.println(listMock.get(1));
    }

    private static int evenInt() {
        return Mockito.intThat(new ArgumentMatcher<Integer>() {
            @Override
            public boolean matches(Object arg) {
                return ((Integer) arg) % 2 == 0;
            }
        });
    }
}

Данная программа выведет 
>> "odd"
>> "even"


Добавим немного static import и посмотрим на результат:
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import java.util.List;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class MockitoDemo6 {

    public static void main(String[] args) {
        // arrange
        List<String> listMock = mock(List.class);
        when(listMock.get(anyInt())).thenReturn("odd");
        when(listMock.get(evenInt())).thenReturn("even");
        // act
        System.out.println(listMock.get(0)); // print "even"
        System.out.println(listMock.get(1)); // print "odd"
    }

    private static int evenInt() {
        return Mockito.intThat(
                new ArgumentMatcher<Integer>() {
                    @Override
                    public boolean matches(Object arg) {
                        return ((Integer) arg) % 2 == 0;
                    }
                });
    }
}
Красота!

Вторая удивительная особенность Mockito состоит в том, что мы может "опросить" моки после использования - "что они пережили".

м



Материалы
    - Сайт Mockito
    - 22 примера из javadoc класса org.mockito.Mockito

Задания для углубленного изучения 
    - разберитесь в разнице между Dummy, Fake, Stub, Mock, Spy. Будьте в состоянии привести примеры (Refcard, Fawler).
    - This process of defining how a given mock method should behave is called stubbing.
    - разберитесь в отличии mock(..).thenReturn(..) и mock(..).thenAnswer(..)
    - arrange-act-assert cycle
    - BDD vs TDD
    - argument matching (ArgumentMatcher, argThat(..))
    - result matching
    - логические операции AND/OR/NOT над mather-ами ()