Моки в юнит-тестах

Моки это такие классы-заглушки (и соответсвенно объекты), которые позволяют избавиться от внешних зависимостей при модульном (unit) тестировании — ибо тестирование с зависимостями уже интеграционное или системное и требует больших ресурсов, состояния данных и как следствие — большей сложности.

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

В PHPUnit моки встроены в PHPUnit_Framework_TestCase и легко получаются от метода getMock(класс, методы, …). Сам по себе он не только даёт объект, но и определяет класс. Так что на случай использования статического метода, мок должен сработать.

$myMock = $this->getMock('MyParser', array('parseXLS'));

Число вызовов

Особенность моков, в отличие от простых заглушек (stub) в изменяющемся поведении. Поведение определяется правилами которые вы составите, например:

$myMock->expects($this->any())

Это мы получаем мок которому безразлично сколько раз он будет использоваться. Но можно быть указать строже — never,atLeastOnce,once,exactly(3)

Параметры и ожидания

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

$myMock->method('parseXLS')
->with( $this->equalTo('fileAsParam.xls') )
->will($this->returnValue( 'text From XLS File' ))

Вместо чёткого равенства можно использовать и более гибкие greaterThan, stringContains, anything.

А вместо стандартного возвращения значения, можно ..

  • onConsecutiveCalls( значения через запятую ) — в зависимости от порядка вызова метода будет возвращать переданные значения
  • returnArgument(индекс переданного ранее аргумента)
  • returnCallback(вызываемая лямбда)
  • throwException( Эксепшн )

Проверка параметров

Для проверки используется ->with() с одним из встроенных методов:

$this->anything()
$this->contains($value)
$this->arrayHasKey($key)
$this->equalTo($value, $delta, $maxDepth)
$this->classHasAttribute($attribute)
$this->greaterThan($value)
$this->isInstanceOf($className)
$this->isType($type)
$this->matchesRegularExpression($pattern)
$this->stringContains($string, $case)

Проблемы

Я сталкивался с тем что фреймворк определяет класс и как следствие если в в дальнейшем инклудите реальный код, то получаете ошибку cannot redeclare class. Пока что это исправлял через использование пространства имён

Более критическая проблема с конструкторами — если вы используете parent::__construct() и при этом parent создан как мок, то можно получить Fatal Error: Cannot call constructor in ...