Php 如何模拟我能模拟的对象';是否传递给方法以触发异常?

Php 如何模拟我能模拟的对象';是否传递给方法以触发异常?,php,phpunit,automated-tests,mockery,Php,Phpunit,Automated Tests,Mockery,考虑以下方法: function m1() { $ent = new Entity; ... try { $ent->save(); } catch (QueryException $e) { ... } 我必须触发一个异常。最好是有。我该怎么做 另外,我无法将$ent传递到该方法中 UPD让我描述一下我的具体情况,以确认是否需要触发异常。在这里,我试图测试支付系统触发的控制器的动作,以通知用户已付款。在it中,我将

考虑以下方法:

function m1()
{
    $ent = new Entity;
    ...
    try {
        $ent->save();
    } catch (QueryException $e) {
        ...
    }
我必须触发一个异常。最好是有。我该怎么做

另外,我无法将
$ent
传递到该方法中

UPD让我描述一下我的具体情况,以确认是否需要触发异常。在这里,我试图测试支付系统触发的控制器的动作,以通知用户已付款。在it中,我将来自支付系统的所有数据存储在数据库中的
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();
}
但如果输入了不正确的
订单id
,则外部约束将失败,因此我将其更改为:

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

但是这样处理任何数据库异常看起来都不太好,所以我正在寻找一种方法来重新显示异常,除非
$e->errorInfo[1]==1452

您的方法不是为测试而设计的。解决这个问题。如果你做不到,那你就得给猴子打补丁,这是很好的选择

我推荐的方法是让您的测试套件安装自己的优先级自动加载程序。让您的测试用例将一个模拟类注册到该自动加载器中,该自动加载器与类名
实体
关联。您的模拟类将神奇地抛出异常。如果您使用的是PHP7,那么您可以访问匿名类,这使得fixture更容易:
newclass Entity{}


根据公认的答案,mockry在模拟类上使用
重载:
量词来支持这种自动加载技巧。这为您节省了大量工作

最简单的方法是调用一个工厂方法,创建实体的模拟实例。比如:

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;
}

下面是我想到的:

/**
 * @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);
}
我使用
@runinsepareprocess
,因为前面的测试触发相同的操作,因此在
mockry
有机会模拟之前加载该类

至于
@preserveGlobalState disabled
,没有它它就无法工作。正如它所说:

注意:默认情况下,PHPUnit将通过序列化父进程中的所有全局变量并在子进程中取消序列化它们来尝试从父进程保留全局状态。如果父进程包含不可序列化的全局变量,这可能会导致问题。有关如何修复此问题的信息,请参阅名为“@preserveGlobalState”的部分

当我只标记一个测试在一个单独的过程中运行时,我与上面所说的有一点不同,因为我只需要一个测试。不是全班同学


限制性的批评是受欢迎的。

通过调用$ent=$this->getEntity(),
$mock
如何进入SUT
testSomething
;在您的测试方法中(在本例中为testSomething()),因此OP必须更改测试中的方法以使其工作?从$ent=new Entity到$ent=$this->getEntity,这算不上什么大的更改。有什么问题吗?可能OP无法控制库。鉴于OP声明“他无法传入对象”,我认为有理由得出这样的结论:OP也不能修改内部结构。我没有抱怨。这很清楚,也很中肯。在引擎盖下,它使用自动加载技巧将模拟连接到目标类。