2016-12-15 10 views
1

Рассмотрим следующий метод:Как мне высмеять объект, который я не могу передать методу запуска исключения?

function m1() 
{ 
    $ent = new Entity; 
    ... 
    try { 
     $ent->save(); 
    } catch (QueryException $e) { 
     ... 
    } 

Я должен вызвать исключение. Предпочтительно с mockery. Как мне это сделать?

P.S. Я не могу передать $ent в метод.

UPD Позвольте мне описать мой конкретный случай, чтобы подтвердить, нужно ли мне инициировать исключение. Здесь я пытаюсь проверить действие контроллера, которое инициируется платежной системой, чтобы сообщить, что пользователь произвел платеж. В этом я, помимо прочего, храню в базе данных все данные, поступающие из платежной системы, в модель PaymentSystemCallback и свяжу ее с моделью Order, которая создается перед перенаправлением пользователя в платежную систему. Таким образом, это выглядит следующим образом:

function callback(Request $request) 
{ 
    $c = new PaymentSystemCallback; 
    $c->remote_addr = $request->ip(); 
    $c->post_data = ...; 
    $c->headers = ...; 
    ... 
    $c->save(); 

    $c->order_id = $request->request->get('order_id'); 
    $c->save(); 
} 

Но если неправильно order_id приходит, ограничение внешнего терпит неудачу, поэтому я изменить его таким образом:

try { 
    $c->save(); 
} catch (QueryException $e) { 
    return response('', 400); 
} 

Но это не выглядит хорошо обрабатывать любую базу данных исключение таким образом, поэтому я ищу способ реконструировать исключение, кроме $e->errorInfo[1] == 1452.

ответ

1

И вот что я придумал:

/** 
* @runInSeparateProcess 
* @preserveGlobalState disabled 
*/ 
function testExceptionOnSave() 
{ 
    $this->setUpState(); 

    Mockery::mock('overload:App\PaymentSystemCallback') 
     ->shouldReceive('save') 
     ->andReturnUsing(function() {}, function() { 
      throw new QueryException('', [], new Exception); 
     }); 

    $this->doRequest(); 

    $this->assertBalanceDidntChange(); 
    $this->assertNotProcessed(); 
    $this->seeStatusCode(500); 
} 

Я использую @runInSeparateProcess, потому что предыдущие тесты вызывают такое же действие, и, следовательно, класс загружается перед тем mockery есть шанс поглумиться над его ,

Что касается @preserveGlobalState disabled, он не работает без него. Как phpunit's documentation выразился:

Примечания: По умолчанию, PHPUnit будет пытаться сохранить глобальное состояние от родительского процесса сериализации всех глобаламов в родительском процессе и десериализации их в дочернем процессе. Это может вызвать проблемы, если родительский процесс содержит глобальные переменные, которые не являются сериализуемыми. См. Раздел «@preserveGlobalState» для получения информации о том, как исправить это.

Я немного отклоняюсь от того, что mockery's documentation говорит, когда я отмечаю только один тест для запуска в отдельном процессе, так как он мне нужен только для одного теста. Не весь класс.

Сложная критика приветствуется.

+0

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

0

Ваш метод не предназначен для тестирования. Исправьте это. Если вы не можете, тогда вам нужно запланировать обезьяну, которая PHP does not support natively.

Мой рекомендуемый подход заключается в том, чтобы ваш тестовый пакет установил собственный автозагрузчик приоритетов. Попросите свой тестовый регистр зарегистрировать класс-макет в этом автозагрузчике, связанный с именем класса Entity. Ваш макет класс будет делать свою магию, чтобы выбросить исключение. Если вы используете PHP 7, у вас есть доступ к анонимным классам, что упрощает монтаж: new class Entity {}.

В соответствии с принятым ответом Mockery поддерживает этот автозагрузчик с использованием квантора overload: на издевающихся классах. Это экономит много работы с вашей стороны!

0

Самый простой способ - вызвать фабричный метод, создающий макет вашего объекта. Что-то вроде:

function testSomething() 
{ 
    $ent = $this->getEntity(); 
    ... 
    try { 
     $ent->save(); 
    } catch (QueryException $e) { 
     ... 
    } 
} 

function getEntity() 
{ 
    $mock = $this->createMock(Entity::class); 
    $mock 
     ->method('save') 
     ->will($this->throwException(new QueryException)); 

    return $mock; 
} 
+1

Как «$ mock» входит в SUT 'testSomething'? – bishop

+0

Вызывая $ ent = $ this-> getEntity(); из тестового метода (в этом случае testSomething() –

+0

Итак, OP должен изменить тестируемый метод, чтобы сделать эту работу? – bishop