Тестирование в Android с Mockito

В прошлой статье я рассказывал о unit-тестировании с Robolectric. Robolectric позволяет запускать тесты прямо в виртуальной машине Java, без использования эмулятора или реального устройства.Mockito позволяет создавать mock и spy классы для проверки различного поведения кода.

Настройка Mockito (Gradle)

Если вы используете Gradle, интеграция Mockito в ваш проект проходит проще простого. Просто добавьте:

dependencies {
    ...
    testCompile 'org.mockito:mockito-core:1.10.19'
    ...
}

Если вы не используете Gradle, вы можете всегда скачать jar-файл из репозитория Mockito и добавьте его в ваш Android-проект. Сейчас 1.10.19 последняя стабильная версия.

Мне нужно создать простую систему кеширования со следующим интерфейсом:

public interface CacheManager {
    public void put(String id, String json) throws IOException;
    public String get(String id) throws IOException;
}

Один из вариантов реализации этого кеша — это кеширование в файл(ы). А вот код реализациии этого кеша:

public class FileCacheManager implements CacheManager {
    private final String logTag = FileCacheManager.class.getSimpleName();
    private Context context;
    public FileCacheManager(Context context) {
        this.context = context;
    }
    @Override
    public void put(String id, String json) throws IOException {
        Closer closer = Closer.create();
        try {
            FileOutputStream outputStream = closer.register(context.openFileOutput(id, Context.MODE_PRIVATE));
            outputStream.write(json.getBytes());
        } catch (IOException e) {
            Log.e(logTag, "Error writing in the cache "+e.getMessage());
            throw new IOException("Error writing in the cache "+e.getMessage());
        }finally {
            try {
                closer.close();
            } catch (IOException e) {
                Log.w(logTag, "Warning! Something went wrong closing outputStream. Ignore this message");
                // ignore ...
            }
        }
    }
    @Override
    public String get(String id) throws IOException {
        FileInputStream inputStream = context.openFileInput(id);
        String storedString = new StringUtils().getStringFromInputStream(inputStream);
        return storedString;
    }

Для реализации этого кеша я использовал класс com.google.common.io.Closer. Вы можете найти более подробную информацию о нем здесь.

Аннотации и установка Mockito

Последняя версия Mockito позволяет создавать нам mock-классы с помощью аннотаций. В этом примере я буду использовать реальный экземпляр класса  FileCacheManager и встрою в него mock-объект класса Context. Mock-объект будет реализовывать нужное нам поведение при вызове его методов.

@RunWith(RobolectricCustomTestRunner.class)
public class FileCacheManagerTest {
    @Mock Context fakeContext;
    CacheManager cacheManager;
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        cacheManager = new FileCacheManager(fakeContext);
    }
...
}

Как вы можете видеть, я использую запускатель тестов от Robolectric, т. к. в моем коде используются классы Android SDK.

Аннотация @Mock, стоящая перед объявлением переменной, создает mock-объект и присваивпет его переменной.

Очень важно при использовании аннотаций инициализировать mock’и используя:

MockitoAnnotations.initMocks(this);

Mock и verify

Ок, что мы имеем? У нас есть реальный объект класса FileCacheManager и mock класса Context. Сейчас нам нужно проверить некоторое поведение. Один из моих тестов: нужно проверить, что при вызове метода put у cacheManager вызывается метод write у fakeOutputStream:

@Test
    public void verifyOutputStreamCalls() throws Exception {
        String json = "{'json':'anyJson'}";
        when(fakeContext.openFileOutput("jsonModel", Context.MODE_PRIVATE)).thenReturn(fakeOutputStream);
        cacheManager.put("jsonModel", json);
        Mockito.verify(fakeOutputStream).write(json.getBytes());
        Mockito.verify(fakeOutputStream).close();
    }

Здесь мы имеем три отдельные части:

  • Первая часть является подготовкой теста, где я создаю переменные, которые я буду использовать в тестах, и устанавливаю поведение объекта. В этом случае, конструкция when… thenReturn, что я подделывает поведение объекта, говоря Mockito вернуть мне fakeOutputStream, при вызове метода openFileOutput.
  • Вторая часть
  • Третья часть это проверка. Mockito «запоминает» какие методы были вызваны во время работы теста. В этом случае, мы убедились, что у объекта fakeOutputStream вызываются методы write и close.

Использование методов when…thenReturn и verify требует, чтобы объекты были mock’ами или шпионами (spy). Вы не можете проверять поведение реальных объектов.

Выбрасывание исключений

Тестирование только благополучных исходов не очень полезно, не так ли? Давайте проверим те случаи, когда может возникнуть ошибка. Итак, что же произойдет, если в файле есть ошибка? Давайте проверим это.

@Test(expected = IOException.class)
   public void verifyOutputStreamClosesWithIOException() throws Exception {
       String json = "{'json':'anyJson'}";
       when(fakeContext.openFileOutput("jsonModel", Context.MODE_PRIVATE)).thenReturn(fakeOutputStream);
       doThrow(new IOException()).when(fakeOutputStream).write(json.getBytes());
       cacheManager.put("jsonModel", json);
       Mockito.verify(fakeOutputStream).close();
   }

Опять же, у нас есть 3 части, но в первом разделе мы добавили новое условие. Использование «doThrow…when» позволяет нам бросать исключения в любое время, когда мы хотим этого от mock-объекта. В этом случае, когда у mock-объекта fakeOutputStream  вызывается метод write будет выброшено исключение IOException (говорит о том, что файл поврежден).

В этом тесте мы убедились, что OutputStream закрыт и что мы ожидаем исключение: @Test(expected = IOException.class) — это специальная аннотация из JUnit4.

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

Источник: Testing in Android with Mockito

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Ваш комментарий будет опубликован после модерации