英文:
Extend Mockery::on assertions for common Use Cases
问题
Is extending Mockery for commonly used assertions possible?
断言模拟函数接收类的实例并匹配标识符是否可能。
Mockery::on(
fn ($arg) => $arg instanceof App\Models\User && $arg->id === $user->id
)
Is it possible to...
-
Simplify this call by extending Mockery (via a Macro?) so that this assertion could be
Mockery::onUser($user)
orMockery::onUserOfId($id)
? -
Pass in multiple arguments like
Mockery::onUserOfRole($id, $user->role)
?
Note: the $user
object instance provided to the Mockery::on
call in the Test is not the same instance as is passed to the mocked function being tested, hence checking the type and matching the identifier.
Possible Solution ... that's not flexible
A possible solution we have explored is extending the base TestCase
class with a matchModel()
function, but this extension is not "elegant". I have considered breaking it out into a Trait, but attaching these functions to all Test Cases, $this->onUserOfRole()
seems wrong. The solution also fails or needs to be customized. (Illuminate\Database\Eloquent\Model@is()
works, but...) if the Object is not a Laravel Model, then we're back to square one:
# 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;
});
}
}
英文:
Is extending Mockery for commonly used assertions possible?
Asserting that a mocked function receives an instance of a class AND matching an identifier is cumbersome.
Mockery::on(
fn ($arg) => $arg instanceof App\Models\User && $arg->id === $user->id
)
Is it possible to...
-
Simplify this call by extending Mockery (via a Macro?) so that this assertion could be
Mockery::onUser($user)
orMockery::onUserOfId($id)
? -
Pass in multiple arguments like
Mockery::onUserOfRole($id, $user->role)
?
Note: the $user
object instance provided to the Mockery::on
call in the Test is not the same instance as is passed to the mocked function being tested, hence checking the type and matching the identifier.
Possible Solution ... that's not flexable
A possible solution we have explored is extending the base TestCase
class with a matchModel()
function, but this extension is not "elegant". I have considered breaking it out into a Trait, but attaching these functions to all Test Cases, $this->onUserOfRole()
seems wrong. The solution also fails or needs to be customized. (Illuminate\Database\Eloquent\Model@is()
works, but...) if the Object is not a Laravel Model, then we're back to square one:
# 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;
});
}
}
答案1
得分: 1
The Mockery::on method 是对\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;
}
/**
* 检查实际值是否与期望值匹配。
*
* @param mixed $actual
* @return bool
*/
public function match(&$actual)
{
return
is_a($actual, $this->expectedClass, true)
&&
$actual->{$this->keyProperty} === $this->keyValue;
}
/**
* 返回此匹配器的字符串表示
*
* @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
中返回布尔结果。
英文:
The Mockery::on method is just a one-line wrapper around the constructor for the \Mockery\Matcher\Closure
class, which in turn defines only two pieces of custom logic:
- a
match
method which takes the parameter being examined, and returns true or false - a
__toString
method returning the fixed string'<Closure===true>'
, presumably for use in error/debug messages
The actual closure isn't defined directly as a property, it just relies on the constructor inherited from MatcherAbstract
which sets an untyped property $_expected
, but performs no other logic directly.
So a replacement for your use case can simply over-ride that constructor with whatever arguments you want, and use them in the match
method to implement your test, e.g.
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}>";
}
}
Then this:
Mockery::on(
fn ($arg) => $arg instanceof App\Models\User && $arg->id === $user->id
)
Becomes this:
new MyMatcher(App\Models\User::class, 'id', $user->id)
If you want to shorten the new MyMatcher
further, you can put a function wherever you like that wraps it in the same Mockery::on
wraps new \Mockery\Matcher\Closure
.
If you want more complex or configurable tests, all you need to figure out is a way to return a boolean result from match
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论