一旦我们需要的第三方库准备好了,我们就准备使用PHP8.0.15.

单元测试的集中式setUp()函数处理类mock的构造函数填充.

使用phpunit v9.5.14目前,我们得到了响应为100的失败测试

我们没有在已知的任何地方使用代码库中的命名参数.

if (empty($this->constructorArgs)) {
    $this->constructorArgs = array('User');
}
if (!empty($this->constructorArgs) && is_array($this->constructorArgs)) {
    foreach ($this->constructorArgs as $classname) {
        if (is_array($classname)) {
            $args[key($classname)] = current($classname);
            $classname = key($classname);
        } else {
            if ($classname == "Twig" || $classname == "Twig\Environment") {
                $args[$classname] = TwigFactory::mockTwig();
            } else {
                $args[$classname] = $this->getMockBuilder($classname)->disableOriginalConstructor()->getMock();
            }
        }
        $container->set($classname, $args[$classname]);
    }
}

$this->mock = $this->getMockBuilder($this->class)
    ->setMethods($this->methods)
    ->setConstructorArgs($args)
    ->getMock();   <-- Error states this line, unfortunately no stack trace

ConstructorArg按如下方式填充到设置中:

$this->constructorArgs = array('User','AnotherClass', 'YetAnother');

我们已经try 了我们能想到的一切,认为这可能与类构造中变量名的大小写有关,即"User$User",但到目前为止,还没有解决这个问题.

推荐答案

在开始之前,让我们创建一个minimal, reproducible example:

class User {}

class Example {
     public User $user;

     public function __construct(User $user) {
          $this->user = $user;
     }
}

class ExampleTest extends PHPUnit\Framework\TestCase {
     public function testExample() {
          $args = [];
          $args['User'] = $this->getMockBuilder('User')->disableOriginalConstructor()->getMock();

          $mock = $this->getMockBuilder(Example::class)
                ->setConstructorArgs($args)
                ->getMock();

          $this->assertTrue(true);
      }
}

在PHP7.4和PHPUnit 9.5.14下运行,这就通过了;对于PHP 8.0和同一个库,它会给出您报告的错误:

错误:未知的命名参数$User

实际上,我们可以进一步简化:我们可以说$args['User'] = new User;,而不是$args['User'] = $this->getMockBuilder('User')->disableOriginalConstructor()->getMock();,得到同样的错误.

现在,让我们看看我们在做什么:

  1. 我们创建一个关联数组,将类名映射到(mock)对象
  2. 我们将该关联数组传递给模拟生成器的setConstructorArgs方法
  3. 奇迹发生了...

那么,会发生什么?也许the source of PHPUnit会提供一些线索.

setConstructorArgs just sets a property,也就是used in getMock,然后通过一系列不同的方法;最终,它会传递到MockObject\Generator::getObject,如果我们go 掉一些错误处理,它会执行以下操作:

$class = new ReflectionClass($className);
$object = $class->newInstanceArgs($arguments);

那么,让我们看看是否可以用它来解决我们的问题:

class User {}

class Example {
     public User $user;

     public function __construct(User $user) {
          $this->user = $user;
     }
}

$class = new ReflectionClass(Example::class);
$object = $class->newInstanceArgs(['User' => new User]);

由于这是自包含的代码,我们可以使用方便的在线工具at https://3v4l.org来比较不同PHP版本的输出:https://3v4l.org/QU4jS

正如所料,PHP7.4对此表示满意,PHP8.0及以上版本给出了一个错误:

Fatal error: Uncaught 错误:未知的命名参数$User in /in/QU4jS:14
Stack trace:
#0 /in/QU4jS(14): ReflectionClass->newInstanceArgs(Array)
#1 {main}
  thrown in /in/QU4jS on line 14

那么,这是怎么回事?the manual page for ReflectionClass::newInstanceArgs(目前)并没有说明所提供的数组应该是什么样子,也没有说明命名参数支持,但我们可以进行一个有根据的猜测:它试图将我们的关联数组作为命名参数匹配到构造函数.以前的版本由于没有命名参数,所以只会忽略键并按顺序应用参数.

我们可以很容易地测试这个理论,方法是将一个类的构造函数设置为两个参数:

class Example2 {
    public function __construct($first, $second) {
        echo "$first then $second\n";
    }
}

$class = new ReflectionClass(Example2::class);
$object = $class->newInstanceArgs(['second' => 'two', 'first' => 'one']);

当值为run on multiple versions时,我们可以看到旧版本的PHP根据数组的order输出"二加一";而较新的版本会根据数组的keys输出"一到二".


那么,长话短说,解决办法是什么?很简单,不要在构造函数参数数组中使用键:

class ExampleTest extends PHPUnit\Framework\TestCase {
     public function testExample() {
          $args = [];
          $args[] = new User;

          $mock = $this->getMockBuilder(Example::class)
                ->setConstructorArgs($args)
                ->getMock();

          $this->assertTrue(true);
      }
}

如果您需要在设置逻辑期间使用它们来跟踪事物,只需在传递它们时使用array_values即可:

class ExampleTest extends PHPUnit\Framework\TestCase {
     public function testExample() {
          $args = [];
          $args['User'] = new User;

          $mock = $this->getMockBuilder(Example::class)
                ->setConstructorArgs(array_values($args))
                ->getMock();

          $this->assertTrue(true);
      }
}

Php相关问答推荐

419注册期间Laravel 11中的错误

将一个数字分配到一系列因素中(就像贪婪算法)

如何在数据库中添加注册表单中的动态新行?

累加平面数组中数字子集的所有组合,以生成组合及其乘积的二维数组

PHP SimpleXML:按属性值访问 node

在WooCommerce上设置简单和可变产品的最小订单量

如何使用PHP从TXM(XML)标记中读取特定属性

如何从phpseclib和SFTP获取日志(log)记录?

隐藏WooCommerce管理子菜单;主页;来自store 经理

从 WooCommerce 购物车中删除总计部分,同时保留小计行

laravel Eloquent 模型的问题

使用 wp_footer 挂钩添加一些 CSS 不起作用

如何为自定义帖子类型的自定义角色提供功能

WooCommerce 有关新订单的附加邮箱通知,其中包含具体详细信息

WooCommerce checkout 流程中基于所选选项添加自定义费用

批量作业(job)提交错误无法处理所有文档,uris 似乎正确?

如何使用动态变量定义 bind_result?

Eloquent 的模型更新了它应该 laravel php 的更多行

如何从 PHP 中的字符串位置排除图像扩展名

使用 splat 运算符时按引用传递 (...)