是否可以为常用断言扩展Mockery?
断言一个模拟函数接收一个类的实例并匹配一个标识符是很麻烦的。
Mockery::on(
fn ($arg) => $arg instanceof App\Models\User && $arg->id === $user->id
)
通过扩展 Mockery(通过宏?)简化此调用,以便此断言可以是
Mockery::onUser($user)
或 Mockery::onUserOfId($id)
?
传入多个参数,如
Mockery::onUserOfRole($id, $user->role)
?
注意:在测试中提供给
$user
调用的 Mockery::on
对象实例 not 与传递给被测试的模拟函数的实例相同,因此检查类型并匹配标识符。
我们探索过的一种可能的解决方案是使用
TestCase
函数扩展matchModel()
基类,但这种扩展并不“优雅”。我考虑过把它分解成一个 Trait,但是把这些函数附加到所有的 Test Cases 上,$this->onUserOfRole()
似乎是错误的。该解决方案也失败或需要定制。 (Illuminate\Database\Eloquent\Model@is()
有效,但是......)如果对象不是 Laravel 模型,那么我们回到原点:
# Possible Solution that is not extensible and only works on Models
class TestCase extends Illuminate\Foundation\Testing\TestCase
protected function matchModel(Model $model)
{
return new MockeryClosure(function ($arg) use ($model) {
if ($model->is($arg)) {
return true;
}
// print custom error message
return false;
});
}
}
\Mockery\Matcher\Closure
类的构造函数的单行包装器,它反过来只定义了两个自定义逻辑:
match
方法接受被检查的参数,并返回 true 或 false__toString
方法返回固定字符串 '<Closure===true>'
,大概用于错误/调试消息实际的闭包没有直接定义为一个属性,它只是依赖于继承自
MatcherAbstract
的构造函数,它设置了一个无类型的属性$_expected
,但不直接执行其他逻辑。
因此,您的用例的替代品可以简单地用您想要的任何参数覆盖该构造函数,并在
match
方法中使用它们来实现您的测试,例如
class MyMatcher extends Mockery\Matcher\MatcherAbstract
{
private string $expectedClass;
private string $keyName;
private mixed $keyValue;
public function __construct(string $expectedClass, string $keyName, mixed $keyValue)
{
$this->expectedClass = $expectedClass;
$this->keyName = $keyName;
$this->keyValue = $keyValue;
}
/**
* Check if the actual value matches the expected.
*
* @param mixed $actual
* @return bool
*/
public function match(&$actual)
{
return
is_a($actual, $this->expectedClass, true)
&&
$actual->{$this->keyProperty} === $this->keyValue;
}
/**
* Return a string representation of this Matcher
*
* @return string
*/
public function __toString()
{
return "<Is {$this->expectedClass} and has {$this->keyProperty} of {$this->keyValue}>";
}
}
然后这个:
Mockery::on(
fn ($arg) => $arg instanceof App\Models\User && $arg->id === $user->id
)
变成这样:
new MyMatcher(App\Models\User::class, 'id', $user->id)
如果你想进一步缩短
new MyMatcher
,你可以在任何你喜欢的地方放置一个函数,将它包装在相同的Mockery::on
包装new \Mockery\Matcher\Closure
.
如果你想要更复杂或可配置的测试,你需要弄清楚的是一种从
match
返回布尔结果的方法。