Laravel框架类在PHPUnit数据提供程序中不可用


Laravel framework classes not available in PHPUnit data provider

我在Laravel中设置了类似以下内容的内容:

/app/controllers/MyController.php

class MyController extends BaseController {
    const MAX_FILE_SIZE = 10000;
    // ....
}

/app/tests/MyControllerTest.php

class MyControllerTest extends TestCase {
    public function myDataProvider() {
        return [
            [ MyController::MAX_FILE_SIZE ]
        ];
    }
    /**
     * @dataProvider myDataProvider
     */
    public function testMyController($a) {
        // Just an example
        $this->assertTrue(1 == 1);
    }
}

但是,当我运行vendor/bin/phpunit时,出现以下错误:

PHP 致命错误:在第 3 行的/home/me/my-app/app/controllers/BaseController.php 中找不到类"控制器"致命错误:在第 3 行的/home/me/my-app/app/controllers/BaseController.php 中找不到类"控制器">

如果我删除了对 myDataProvider()MyController 类的引用并将其替换为文字常量,则测试成功完成。

此外,我可以在实际的testMyController()方法中放置对MyController::MAX_FILE_SIZE的引用,并且测试也成功完成。

Laravel框架类的自动加载设置似乎直到调用数据提供程序方法之后,但在调用实际测试方法之前才设置。有什么办法可以解决这个问题,以便我可以从 PHPUnit 数据提供程序中访问 Laravel 框架类?


注意:我直接从命令行调用 PHPUnit,而不是从 IDE(如 NetBeans(中调用。我知道有些人对此有问题,但我认为这不适用于我的问题。

正如这个答案所暗示的,这似乎与 PHPUnit 在任何测试用例中调用任何数据提供程序和setUp()方法的顺序有关。

PHPUnit 将在运行任何测试之前调用数据提供程序方法。在每次测试之前,它还将在测试用例中调用 setUp() 方法。Laravel钩入setUp()方法以调用$this->createApplication(),该方法会将控制器类添加到"包含路径"中,以便可以正确自动加载它们。

由于数据提供程序

方法是在发生这种情况之前运行的,因此对数据提供程序中的控制器类的任何引用都将失败。可以通过将测试类修改为如下所示来解决此问题:

class MyControllerTest extends TestCase {
    public function __construct($name = null, array $data = array(), $dataName = '') {
        parent::__construct($name, $data, $dataName);
        $this->createApplication();
    }
    public function myDataProvider() {
        return [
            [ MyController::MAX_FILE_SIZE ]
        ];
    }
    /**
     * @dataProvider myDataProvider
     */
    public function testMyController($a) {
        // Just an example
        $this->assertTrue(1 == 1);
    }
}

这将在运行数据提供程序方法之前调用createApplication(),因此存在一个有效的应用程序实例,该实例将允许正确自动加载相应的类。

这似乎有效,但我不确定它是否是最好的解决方案,或者它是否可能导致任何问题(尽管我想不出任何理由(。

如果直接在 dataProvider 方法中创建应用程序,则测试的初始化速度会快得多,尤其是在有大量要测试的项时。

public function myDataProvider() {
    $this->createApplication();
    return [
        [ MyController::MAX_FILE_SIZE ]
    ];
}

其他解决方案的性能警告(特别是如果您计划在 dataProvider 中使用工厂(:

正如本文所解释的:

测试

运行程序通过扫描所有测试来构建测试套件 目录 [...]当一个 找到批注@dataProvider,则引用的数据提供程序为 执行,然后创建一个测试用例并将其添加到测试套件中 提供程序中的每个数据集。

[...]

如果在数据提供程序中使用工厂方法,则这些 工厂将使用此数据提供程序为每个测试运行一次 在您的第一次测试运行之前。所以一个数据提供者[...]被十个测试使用。 将在您的第一次之前运行十次 测试甚至运行。这可能会大大减慢时间,直到您的 执行第一个测试。甚至 [...] 使用phpunit --filter, 每个数据提供程序仍将运行多次。过滤在测试后发生 套件已生成,因此在任何之后 数据提供程序已执行。

上面的文章建议从 dataProvider 返回一个闭包,并在测试中执行它:

/** 
 * @test
 * @dataProvider paymentProcessorProvider
 */
public function user_can_charge_an_amount($paymentProcessorProvider)
{
    $paymentProcessorProvider();
    $paymentProcessor = $this->app->make(PaymentProviderContract::class);
    $paymentProcessor->charge(2000);
    $this->assertEquals(2000, $paymentProcessor->totalCharges());
}
public function paymentProcessorProvider()
{
    return [
        'Braintree processor' => [function () {
            $container = Container::getInstance();
            $container->bind(PaymentProviderContract::class, BraintreeProvider::class);
        }],
        ...
    ];
}
您可以通过将

自定义引导程序添加到项目中来调整 PHPUnit 的这种行为,phpunit.xml如下所示(请看第 3 行(:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         bootstrap="tests/bootstrap.php" ← ← ← THIS
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
...
</phpunit>

然后在您的 tests 文件夹中创建一个bootstrap.php文件(即您上面表示的路径(,并粘贴以下内容:

<?php
use Illuminate'Contracts'Console'Kernel;
require __DIR__ . '/../vendor/autoload.php';
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap();

您现在可以在数据提供程序中使用Laravel功能,请记住,它们仍然在您的setUp方法之后运行。