Apex предоставляет stub API для реализации Mocking Framework. У Mocking Framework (моков) есть много преимуществ. Он может упростить и улучшить тестирование и помочь создать более быстрые и надежные тесты. Можно использовать его для изолированного тестирования классов и, что важно, для юнит тестирования.
Создание моков с помощью API-заглушки (стаб) также может быть полезным, поскольку стаб объекты создаются во время выполнения. Поскольку эти объекты создаются динамически, не нужно упаковывать и деплоить тестовые классы. Можно создать свой собственный мок или использовать созданный кем-то другим.
Можно определить поведение стабов, которые создаются во время выполнения как анонимные подклассы классов Apex. API-стаб включает интерфейс System.StubProvider
и метод System.Test.createStub()
.
Давайте рассмотрим пример, чтобы показать, как работает API-стаб. Мы сосредоточимся на механике использования API стаб Apex и не будем рассматривать все возможные варианты применения моков.
Для примера используем немного измененный код, приведенный в документации Apex Developer Guide, который выводит фразу 'We know that now - '
с указанием текущего времени.
public class TimeFormatter {
public String getFormattedTime(TimeHelper helper) {
return 'We know that now - ' + helper.getTimeNow();
}
}
Для вызова этого метода мы передаем вспомогательный класс, у которого есть метод, возвращающий сегодняшнюю дату.
public class TimeHelper {
public String getTimeNow() {
DateTime now = DateTime.now();
Integer hours = now.hour(), minutes = now.minute(), seconds = now.second();
String timeNow = (hours + ':' + minutes + ':' + seconds);
return timeNow;
}
}
Для вызова используем следующий код
TimeFormatter tf = new TimeFormatter();
TimeHelper th = new TimeHelper ();
System.debug(tf.getFormattedTime(th));
Чтобы протестировать данный класс, мы изолируем метод getFormattedTime()
, чтобы убедиться, что форматирование работает правильно. Возвращаемое значение метода getFormattedTime()
обычно зависит от времени. Однако в этом случае мы хотим вернуть постоянное предсказуемое значение, чтобы изолировать наше тестирование от форматирования. Вместо того, чтобы написать «фейковую» версию класса, в которой метод возвращает постоянное значение, мы создаем стаб версию класса. Стаб объект создается динамически во время выполнения, и мы можем указать «стаб» поведение его метода.
Чтобы использовать стаб версию класса Apex необходимо:
- Определить поведение стаб класса, реализовав интерфейс
System.StubProvider
- Создать экземпляр стаб-объекта с помощью метода
System.Test.createStub()
- Вызвать соответствующий метод стаб-объекта из тестового класса.
Реализуем интерфейс StubProvider
@isTest
public class MockProvider implements System.StubProvider {
public Object handleMethodCall(Object stubbedObject, String stubbedMethodName,
Type returnType, List<Type> listOfParamTypes, List<String> listOfParamNames,
List<Object> listOfArgs) {
if (returnType.getName() == 'String')
return '00:00:00';
else
return null;
}
}
StubProvider - это интерфейс колбек вызова. Он определяет единственный метод, который требует реализации: handleMethodCall()
. Когда вызывается заглушенный (стабируемый) метод, вызывается handleMethodCall()
. В этом методе мы определяем поведение стабируемого класса. Метод имеет следующие параметры:
- stubbedObject: стабируемый объект
- stubbedMethodName: имя вызванного метода
- returnType: возвращаемый тип вызванного метода
- listOfParamTypes: список типов параметров вызванного метода
- listOfParamNames: список имен параметров вызванного метода
- listOfArgs: фактические значения аргументов, переданные в этот метод во время выполнения.
Можно использовать эти параметры, чтобы определить, какой метод класса был вызван, а затем можно определить поведение для каждого метода. В этом случае мы проверяем тип возвращаемого значения метода, чтобы идентифицировать его и возвращать захардкодженное значение.
Создание экземпляра стаб класса
Следующим шагом является создание экземпляра стаб класса. Следующий служебный класс возвращает стаб объект, который можно использовать в качестве имитации.
public class MockUtil {
private MockUtil(){}
public static MockProvider getInstance() {
return new MockProvider();
}
public static Object createMock(Type typeToMock) {
return Test.createStub(typeToMock, MockUtil.getInstance());
}
}
Этот класс содержит метод createMock()
, который вызывает метод Test.createStub()
. Метод createStub()
принимает тип класса Apex и экземпляр интерфейса StubProvider
, который мы создали ранее. Он возвращает стаб объект, который мы можем использовать при тестировании.
Вызов метода стаб класса.
Наконец мы подошли к вызову соответствующего метода стаб класса из тестового класса:
@isTest
public class TimeFormatterTest {
@isTest
public static void testGetFormattedTime() {
TimeHelper mockTH = (TimeHelper)MockUtil.createMock(TimeHelper.class);
TimeFormatter tf = new TimeFormatter();
System.assertEquals('We know that now - 00:00:00', tf.getFormattedTime(mockTH));
}
}
В этом тесте мы вызываем метод createMock()
, чтобы создать стаб класса TimeHelper
. Затем мы можем вызвать метод getFormattedTime()
для стаб объекта, который возвращает наше захардкодженное время. Использование жестко заданного времени позволяет нам тестировать поведение метода getFormattedTime()
изолированно.
Ограничения API Apex Stub
Необходимо помнить о следующих ограничениях при работе с API стаб Apex:
- Стабируемый объект должен находиться в том же пространстве имен, что и вызов метода Test.createStub(). Однако реализация интерфейса StubProvider может находиться в другом пространстве имен.
- Для написания стаб классов нельзя использовать следующие элементы Apex:
- Статические методы (включая future методы);
- Private методы;
- Геттеры и сеттеры;
- Триггеры;
- Inner классы;
- Системные классы (System Namespace);
- Классы, реализующие интерфейс Batchable;
- Классы, у которых есть только private конструкторы;
- Итераторы нельзя использовать в качестве возвращаемых типов или типов параметров.
Подробней про StubProvider интерфейс можно почитать в документации
StubProvider Interface
При написании этой статьи были использованы данные из документации
Apex Developer Guide