英文:
Replace PHPUnit method `withConsecutive` (abandoned in PHPUnit 10)
问题
方法withConsecutive
将在PHPUnit 10中被删除(在9.6中已弃用),我需要将所有出现该方法的地方替换为新代码。
尝试寻找一些解决方案,但没有找到任何合理的解决方案。
例如,我有以下代码:
$this->personServiceMock->expects($this->exactly(2))
->method('prepare')
->withConsecutive(
[$personFirst, $employeeFirst],
[$personSecond, $employeeSecond],
)
->willReturnOnConsecutiveCalls($personDTO, $personSecondDTO);
我应该用什么代码替换withConsecutive
?
附注:官方网站上的文档仍然显示如何使用withConsecutive
。
英文:
As method withConsecutive
will be deleted in PHPUnit 10 (in 9.6 it's deprecated) I need to replace all of occurrences of this method to new code.
Try to find some solutions and didn't find any of reasonable solution.
For example, I have a code
$this->personServiceMock->expects($this->exactly(2))
->method('prepare')
->withConsecutive(
[$personFirst, $employeeFirst],
[$personSecond, $employeeSecond],
)
->willReturnOnConsecutiveCalls($personDTO, $personSecondDTO);
To which code should I replace withConsecutive
?
P.S. Documentation on official site still shows how use withConsecutive
答案1
得分: 19
我已经用以下代码替换了"withConsecutive"。
$matcher = $this->exactly(2);
$this->service
->expects($matcher)
->method('functionName')
->willReturnCallback(function (string $key, string $value) use ($matcher, $expected1, $expected2) {
match ($matcher->numberOfInvocations()) {
1 => $this->assertEquals($expected1, $value),
2 => $this->assertEquals($expected2, $value),
};
});
如果您需要任何进一步的帮助,请随时告诉我。
英文:
I have replaced withConsecutive with the following.
$matcher = $this->exactly(2);
$this->service
->expects($matcher)
->method('functionName')
->willReturnCallback(function (string $key, string $value) use ($matcher,$expected1, $expected2) {
match ($matcher->numberOfInvocations()) {
1 => $this->assertEquals($expected1, $value),
2 => $this->assertEquals($expected2, $value),
};
});
答案2
得分: 11
我刚刚升级到 PHPUnit 10,并遇到了相同的问题。这是我想到的解决方案:
$this->personServiceMock
->method('prepare')
->willReturnCallback(fn($person, $employee) =>
match([$person, $employee]) {
[$personFirst, $employeeFirst] => $personDTO,
[$personSecond, $employeeSecond] => $personSecondDTO
}
);
如果在match
块中传递的模拟方法与预期不符,PHP会抛出UnhandledMatchError
。
编辑:一些评论指出了这里的限制,即不知道函数被调用了多少次。这有点取巧,但我们可以手动计算函数调用的次数,如下所示:
// 保留传递的参数的引用数组:
$callParams = [];
$this->personServiceMock
->method('prepare')
// 通过引用传递callParams数组:
->willReturnCallback(function($person, $employee) use (&$callParams) {
// 将当前参数存储在数组中:
array_push($callParams, func_get_args());
match([$person, $employee]) {
[$personFirst, $employeeFirst] => $personDTO,
[$personSecond, $employeeSecond] => $personSecondDTO
}
});
// 检查$callParams数组中是否存在预期的参数调用:
self::assertContains(["Person1", "Employee1"], $callParams);
英文:
I've just upgraded to PHPUnit 10 and faced the same issue. Here's the solution I came to:
$this->personServiceMock
->method('prepare')
->willReturnCallback(fn($person, $employee) =>
match([$person, $employee]) {
[$personFirst, $employeeFirst] => $personDTO,
[$personSecond, $employeeSecond] => $personSecondDTO
}
);
If the mocked method is passed something other than what's expected in the match
block, PHP will throw a UnhandledMatchError
.
Edit: Some comments have pointed out the limitation here of not knowing how many times the function has been called. This is a bit of a hack, but we could count the function calls manually like this:
// Keep reference of the arguments passed in an array:
$callParams = [];
$this->personServiceMock
->method('prepare')
// Pass the callParams array by reference:
->willReturnCallback(function($person, $employee)use(&$callParams) {
// Store the current arguments in the array:
array_push($callParams, func_get_args());
match([$person, $employee]) {
[$personFirst, $employeeFirst] => $personDTO,
[$personSecond, $employeeSecond] => $personSecondDTO
}
});
// Check that an expected argument call is present in the $callParams array:
self::assertContains(["Person1", "Employee1"], $callParams);
答案3
得分: 3
以下是您要翻译的代码部分:
对于我来说,以下的方法有效:
$expected = ['value1', 'value2'];
$matcher = $this->exactly(count($expected));
$this->mockedObject->expects($matcher)->method('test')->with(
$this->callback(function($param) use ($expected) {
$this->assertEquals($param, $expected[$matcher->getInvocationCount() - 1]);
return true;
})
)
英文:
For me the following worked:
$expected = ['value1', 'value2'];
$matcher = $this->exactly(count($expected));
$this->mockedObject->expects($matcher)->method('test')->with(
$this->callback(function($param) use ($expected) {
$this->assertEquals($param, $expected[$matcher->getInvocationCount() - 1]);
return true;
})
)
答案4
得分: 2
以下是您要翻译的内容:
I ran into the same issue and although I don't think this is the most practical solution in the world, you can try it.
You will need a simple helper function.
public function consecutiveCalls(...$args): callable
{
$count = 0;
return function ($arg) use (&count, $args) {
return $arg === $args[count++];
};
}
Then we'll replace deprecated withConsecutive
with with
and for every parameter we'll add a callback that will return our helper function with consecutive parameters.
$this->personServiceMock->expects($this->exactly(2))
->method('prepare')
->with(
self::callback(self::consecutiveCalls($personFirst, $personSecond)),
self::callback(self::consecutiveCalls($employeeFirst, $employeeSecond)),
)
->willReturnOnConsecutiveCalls($personDTO, $personSecondDTO);
英文:
I ran into the same issue and although I don't think this is the most practical solution in the world, you can try it.
You will need a simple helper function.
public function consecutiveCalls(...$args): callable
{
$count = 0;
return function ($arg) use (&$count, $args) {
return $arg === $args[$count++];
};
}
Then we'll replace deprecated withConsecutive
with with
and for every parameter we'll add callback that will return our helper function with consecutive parameters.
$this->personServiceMock->expects($this->exactly(2))
->method('prepare')
->with(
self::callback(self::consecutiveCalls($personFirst, $personSecond)),
self::callback(self::consecutiveCalls($employeeFirst, $employeeSecond)),
)
->willReturnOnConsecutiveCalls($personDTO, $personSecondDTO);
答案5
得分: 0
看起来没有现成的解决方案。所以,我找到了几种解决方法:
- 使用自定义特性,该特性实现了
withConsecutive
方法。 - 使用Prophecy或Mockery进行模拟。
英文:
Looks like there are not exists solution from the box.
So, what I found - several solutions
- Use your own trait which implements method
withConsecutive
- Use prophecy or mockery for mocking.
答案6
得分: 0
我已经创建了一个工厂,用于传递给->willReturnCallback()
PHPUnit方法的回调,代码如下(灵感来源:@Awais Mushtaq):
protected function getReturnCallbackFn(
InvocationOrder $matcher,
array $paramConsuitiveCalls,
array $returnConsuitiveCalls
): \Closure
{
if (!empty($returnConsuitiveCalls) && count($paramConsuitiveCalls) !== count($returnConsuitiveCalls)) {
throw new \InvalidArgumentException('参数和返回值的数量不匹配。');
}
return function (...$args) use (
$matcher,
$paramConsuitiveCalls,
$returnConsuitiveCalls
) {
$i = $matcher->numberOfInvocations() - 1;
if (!array_key_exists($i, $paramConsuitiveCalls)) {
throw new \OutOfRangeException(sprintf(
'期望的迭代次数 [%d] 与当前执行的次数 [%d] 不匹配。',
count($returnConsuitiveCalls),
$matcher->numberOfInvocations()),
);
}
if (empty($args)) {
$this->assertEquals($paramConsuitiveCalls[$i], []);
} else {
foreach ($args as $argI => $arg) {
$this->assertEquals($paramConsuitiveCalls[$i][$argI], $arg);
}
}
if (empty($returnConsuitiveCalls)) {
return;
}
return $returnConsuitiveCalls[$i];
};
}
用法示例:
$params = [[123], [234]];
$ret = [$sampleData1Call, $sampleData2Call];
$matcher = $this->exactly(count($params));
$stub
->method('getById')
->willReturnCallback($this->getReturnCallbackFn($matcher, $params, $ret));
请注意,这是PHP代码的翻译。
英文:
I have created the factory for a callback passed to ->willReturnCallback()
PHPUnit method and it goes like this (inspiration: @Awais Mushtaq):
protected function getReturnCallbackFn(
InvocationOrder $matcher,
array $paramConsuitiveCalls,
array $returnConsuitiveCalls
): \Closure
{
if (!empty($returnConsuitiveCalls) && count($paramConsuitiveCalls) !== count($returnConsuitiveCalls)) {
throw new \InvalidArgumentException('Count of params and return values mismatch.');
}
return function (...$args) use (
$matcher,
$paramConsuitiveCalls,
$returnConsuitiveCalls
) {
$i = $matcher->numberOfInvocations() - 1;
if (!array_key_exists($i, $paramConsuitiveCalls)) {
throw new \OutOfRangeException(sprintf(
'Iterations expected [%d] against current [%d] executed.',
count($returnConsuitiveCalls),
$matcher->numberOfInvocations()),
);
}
if (empty($args)) {
$this->assertEquals($paramConsuitiveCalls[$i], []);
} else {
foreach ($args as $argI => $arg) {
$this->assertEquals($paramConsuitiveCalls[$i][$argI], $arg);
}
}
if (empty($returnConsuitiveCalls)) {
return;
}
return $returnConsuitiveCalls[$i];
};
}
And usage:
$params = [[123], [234]];
$ret = [$sampleData1Call, $sampleData2Call];
$matcher = $this->exactly(count($params));
$stub
->method('getById')
->willReturnCallback($this->getReturnCallbackFn($matcher, $params, $ret))
;
答案7
得分: -1
我们有一个庞大的代码库,经常使用 withConsecutive
。为了避免不得不修复每个测试,我们创建了一个 phpunit-extensions 包来简化过渡。
这种记法应该很容易找到并替换现有的用法:<br>
$mock->method('myMethod')->withConsecutive([123, 'foobar'], [456]);
变成:<br>
$mock->method('myMethod')->with(...\DR\PHPUnitExtensions\Mock\consecutive([123, 'foobar'], [456]));
使用PHPStorm的结构搜索和替换会更容易:
https://www.jetbrains.com/help/phpstorm/structural-search-and-replace.html
英文:
We have a large codebase and used withConsecutive
frequently. To avoid having to fix every test we created a phpunit-extensions package to ease the transition.
The notation should be fairly easy to find and replace existing usages:<br>
$mock->method('myMethod')->withConsecutive([123, 'foobar'], [456]);
To:<br>
$mock->method('myMethod')->with(...\DR\PHPUnitExtensions\Mock\consecutive([123, 'foobar'], [456]));
It's even easier with PHPStorm's structural search and replace:
https://www.jetbrains.com/help/phpstorm/structural-search-and-replace.html
答案8
得分: -1
以下是您要翻译的内容:
"For our applications we use our custom constraint with specific map.
Previously we try to use callback (with call assertEquals
inside), but callback must return only boolean and if we try to check objects, the message was be simple - objects are not equals
, without diff and without any information about problem.
As result, we create our constraint:
<?php
declare(strict_types = 1);
namespace Acme\Tests\PhpUnit\Framework\Constraint;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
use SebastianBergmann\Comparator\ComparisonFailure;
use SebastianBergmann\Comparator\Factory;
class ConsecutiveMatches extends Constraint
{
/**
* @var ComparisonFailure|null
*/
private ?ComparisonFailure $failure = null;
/**
* Constructor.
*
* @param InvocationOrder $invocation
* @param array<int, mixed> $map
* @param bool $strict
*/
public function __construct(
private readonly InvocationOrder $invocation,
private readonly array $map,
private readonly bool $strict = true,
) {
}
/**
* {@inheritdoc}
*/
protected function matches(mixed $other): bool
{
$invokedCount = $this->invocation->numberOfInvocations();
if (array_key_exists($invokedCount - 1, $this->map)) {
$expectedParam = $this->map[$invokedCount - 1];
} else if ($this->strict) {
throw new \InvalidArgumentException(sprintf(
'Missed argument for matches (%d times).',
$invokedCount
));
}
$comparator = Factory::getInstance()->getComparatorFor($expectedParam, $other);
try {
$comparator->assertEquals($expectedParam, $other);
} catch (ComparisonFailure $error) {
$this->failure = $error;
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function failureDescription(mixed $other): string
{
return $this->failure ? $this->failure->getDiff() : parent::failureDescription($other);
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return '';
}
}
In this constraint we get the argument from map by invocation number.
And it very easy to usage:
#[Test]
public function shouldFoo(): void
{
$mock = $this->createMock(MyClass::class);
$matcher = new InvokedCount(2); // Should call 2 times
$mock->expects($matcher)
->method('someMethod')
->with(new ConsecutiveMatches($matcher, [$expectedArgumentForFirstCall, $expectedArgumentForSecondCall]))
->willReturnCallback(function () {
// You logic for return value.
// You can use custom map too for returns.
});
}
As result, we can use our constraint in more places."
英文:
For our applications we use our custom constraint with specific map.
Previously we try to use callback (with call assertEquals
inside), but callback must return only boolean and if we try to check objects, the message was be simple - objects are not equals
, without diff and without any information about problem.
As result, we create our constaint:
<?php
declare(strict_types = 1);
namespace Acme\Tests\PhpUnit\Framework\Constraint;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
use SebastianBergmann\Comparator\ComparisonFailure;
use SebastianBergmann\Comparator\Factory;
class ConsecutiveMatches extends Constraint
{
/**
* @var ComparisonFailure|null
*/
private ?ComparisonFailure $failure = null;
/**
* Constructor.
*
* @param InvocationOrder $invocation
* @param array<int, mixed> $map
* @param bool $strict
*/
public function __construct(
private readonly InvocationOrder $invocation,
private readonly array $map,
private readonly bool $strict = true,
) {
}
/**
* {@inheritdoc}
*/
protected function matches(mixed $other): bool
{
$invokedCount = $this->invocation->numberOfInvocations();
if (\array_key_exists($invokedCount - 1, $this->map)) {
$expectedParam = $this->map[$invokedCount - 1];
} else if ($this->strict) {
throw new \InvalidArgumentException(\sprintf(
'Missed argument for matches (%d times).',
$invokedCount
));
}
$comparator = Factory::getInstance()->getComparatorFor($expectedParam, $other);
try {
$comparator->assertEquals($expectedParam, $other);
} catch (ComparisonFailure $error) {
$this->failure = $error;
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function failureDescription(mixed $other): string
{
return $this->failure ? $this->failure->getDiff() : parent::failureDescription($other);
}
/**
* {@inheritdoc}
*/
public function toString(): string
{
return '';
}
}
In this constraint we get the argument from map by invocation number.
And it very easy to usage:
#[Test]
public function shouldFoo(): void
{
$mock = $this->createMock(MyClass::class);
$matcher = new InvokedCount(2); // Should call 2 times
$mock->expects($matcher)
->method('someMethod')
->with(new ConsecutiveMatches($matcher, [$expectedArgumentForFirstCall, $expectedArgumentForSecondCall]))
->willReturnCallback(function () {
// You logic for return value.
// You can use custom map too for returns.
});
}
As result, we can use our constraint in more places.
答案9
得分: -2
$params = ['foo', 'bar',];
$mockObject->expects($this->exactly(2))
->method('call')
->willReturnCallback(function (string $param) use (&$params) {
$this::assertSame(array_shift($params), $param);
})
->willReturnOnConsecutiveCalls('foo_result', 'bar_result');
或者可以从willReturnCallback
返回结果,而不是使用 willReturnOnConsecutiveCalls
。
英文:
$params = ['foo', 'bar',];
$mockObject->expects($this->exactly(2))
->method('call')
->willReturnCallback(function (string $param) use (&$params) {
$this::assertSame(\array_shift($params), $param);
})
->willReturnOnConsecutiveCalls('foo_result', 'bar_result');
Or instead of using willReturnOnConsecutiveCalls
you can return result from willReturnCallback
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论