如何模拟我可以传递给触发异常的方法的对象?

时间:2016-12-15 16:55:00

标签: php phpunit automated-tests mockery

考虑以下方法:

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

3 个答案:

答案 0 :(得分: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所说的内容,因为我只需要进行一次测试。不是全班。

欢迎批评性批评。

答案 1 :(得分:0)

您的方法不适用于测试。修复它。如果你不能,那么你必须修补补丁,PHP does not support natively

我推荐的方法是让您的测试套件安装自己的优先级自动加载器。让您的测试用例将模拟类注册到与类名Entity相关联的自动加载器中。你的模拟类会发挥它的魔力来抛出异常。如果您正在使用PHP 7,则可以访问匿名类,这使得灯具更容易:new class Entity {}

根据接受的答案,Mockery在模拟类上使用overload:量词支持这种自动加载技巧。这为您节省了大量工作!

答案 2 :(得分: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;
}