PhpUnit 没有用字符串数字和摘要识别 TypeError Class
PhpUnit does not identify TypeError with string numbers and Abstract Class
与以下class
declare(strict_types=1);
abstract class IntValueObject
{
public function __construct(protected int $value)
{
}
}
和测试
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
}
}
return
- Api\Tests\Shared\Domain\ValueObject\IntValueObjectTest::testWithNotValidValue
Failed asserting that exception of type "TypeError" is thrown.
如果我将 $value 从 '1' 更改为 'foo' 如果它通过了测试。
我们使用 PHP 8,在生产中,如果传递值 '1' 会给出 TypeError,为什么在测试中没有发生这种情况?
提前致谢。
“问题”的根源
https://bugs.php.net/bug.php?id=81258
可能的解决方案
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
}
}
我能想到的一个解释是,在测试期间,IntValueObject::__construct('1')
是从未使用 declare(strict_types=1);
的代码中调用的,因此字符串 '1'
被强制转换为整数 1
。在这种情况下不会抛出 TypeError(但对于字符串 'foo'
会抛出 - 正如您在问题中描述的行为)。
原因
原因是使用了生成的模拟:
<?php declare (strict_types = 1);
...
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
...
在这种情况下没有 TypeError 可能出乎你的意料,因为你有标量严格类型,但仍然看到构造函数的字符串 '1'
到整数 1
的类型强制第一个参数。
不匹配
然而 TypeError 是 只有当调用 IntValueObject::__construct(int $value)
的代码有 declare(strict_types=1)
.
虽然测试代码 有 declare(strict_types=1)
它 一定不能 是调用构造函数方法的代码 - 因为没有抛出 TypeError。
真实
幕后 $this->getMockForAbstractClass(...);
使用 Instantiator from the Doctrine project which is making use of PHP reflection (meta-programming)。由于这些方法都是内部代码,declare(strict_types=1)
无效并且没有 TypeError 了。
与以下代码示例比较:
<?php declare(strict_types=1);
class Foo {
public function __construct(int $integer) {
$this->integer = $integer;
}
}
try {
$foo = new Foo('1');
} catch (TypeError $e) {
} finally {
assert(isset($e), 'TypeError was thrown');
assert(!isset($foo), '$foo is unset');
}
$foo = (new ReflectionClass(Foo::class))->newInstance('1');
var_dump($foo);
在启用断言的情况下执行时,输出如下:
object(Foo)#3 (1) {
["integer"]=>
int(1)
}
在 try-block 中,TypeError 被抛出 new
正如你所期望的那样。
但之后用 PHP 反射实例化时就不是了。
补救措施
添加一个 class 到你的测试套件, 是 真正模拟抽象基础 class 并将它放在它的测试旁边,这样你可以轻松使用它:
<?hpp declare(strict_types=1);
class IntValueObjectMock extends IntValueObject
{...}
然后在你的测试中使用IntValueObjectMock:
$value = '1';
$this->expectException(\TypeError::class);
new IntValueObjectMock($value);
或者直接扩展时使用 anonymous class:
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
或者使用 PHP 反射或 run static code-analysis 对您自己的构造函数方法进行类型检查,其好处是无需实例化即可检测到此类问题 - 因此无需额外测试 -涉及代码。
与以下class
declare(strict_types=1);
abstract class IntValueObject
{
public function __construct(protected int $value)
{
}
}
和测试
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
}
}
return
- Api\Tests\Shared\Domain\ValueObject\IntValueObjectTest::testWithNotValidValue Failed asserting that exception of type "TypeError" is thrown.
如果我将 $value 从 '1' 更改为 'foo' 如果它通过了测试。
我们使用 PHP 8,在生产中,如果传递值 '1' 会给出 TypeError,为什么在测试中没有发生这种情况?
提前致谢。
“问题”的根源
https://bugs.php.net/bug.php?id=81258
可能的解决方案
declare(strict_types=1);
final class IntValueObjectTest extends TestCase
{
public function testWithNotValidValue(): void
{
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
}
}
我能想到的一个解释是,在测试期间,IntValueObject::__construct('1')
是从未使用 declare(strict_types=1);
的代码中调用的,因此字符串 '1'
被强制转换为整数 1
。在这种情况下不会抛出 TypeError(但对于字符串 'foo'
会抛出 - 正如您在问题中描述的行为)。
原因
原因是使用了生成的模拟:
<?php declare (strict_types = 1);
...
$this->expectException(\TypeError::class);
$this->getMockForAbstractClass(IntValueObject::class, [$value]);
...
在这种情况下没有 TypeError 可能出乎你的意料,因为你有标量严格类型,但仍然看到构造函数的字符串 '1'
到整数 1
的类型强制第一个参数。
不匹配
然而 TypeError 是 只有当调用 IntValueObject::__construct(int $value)
的代码有 declare(strict_types=1)
.
虽然测试代码 有 declare(strict_types=1)
它 一定不能 是调用构造函数方法的代码 - 因为没有抛出 TypeError。
真实
幕后 $this->getMockForAbstractClass(...);
使用 Instantiator from the Doctrine project which is making use of PHP reflection (meta-programming)。由于这些方法都是内部代码,declare(strict_types=1)
无效并且没有 TypeError 了。
与以下代码示例比较:
<?php declare(strict_types=1);
class Foo {
public function __construct(int $integer) {
$this->integer = $integer;
}
}
try {
$foo = new Foo('1');
} catch (TypeError $e) {
} finally {
assert(isset($e), 'TypeError was thrown');
assert(!isset($foo), '$foo is unset');
}
$foo = (new ReflectionClass(Foo::class))->newInstance('1');
var_dump($foo);
在启用断言的情况下执行时,输出如下:
object(Foo)#3 (1) {
["integer"]=>
int(1)
}
在 try-block 中,TypeError 被抛出 new
正如你所期望的那样。
但之后用 PHP 反射实例化时就不是了。
补救措施
添加一个 class 到你的测试套件, 是 真正模拟抽象基础 class 并将它放在它的测试旁边,这样你可以轻松使用它:
<?hpp declare(strict_types=1);
class IntValueObjectMock extends IntValueObject
{...}
然后在你的测试中使用IntValueObjectMock:
$value = '1';
$this->expectException(\TypeError::class);
new IntValueObjectMock($value);
或者直接扩展时使用 anonymous class:
$value = '1';
$this->expectException(\TypeError::class);
new class($value) extends IntValueObject {};
或者使用 PHP 反射或 run static code-analysis 对您自己的构造函数方法进行类型检查,其好处是无需实例化即可检测到此类问题 - 因此无需额外测试 -涉及代码。