PHPUnit 给出错误:目标 [Illuminate\Contracts\View\Factory] ​​不可实例化

Bar*_*ans 0 php phpunit laravel

我为我的新 Laravel 7 应用程序创建了一个简单的测试。但是当我运行时php artisan test出现以下错误。

Target [Illuminate\Contracts\View\Factory] is not instantiable.

当我在浏览器中转到该页面时,不会出现该错误。

$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Run Code Online (Sandbox Code Playgroud)

Pot*_*rca 5

尽管“只编写功能测试”可能看起来像是一种逃避(“它们不是单元测试!”),但如果您不想陷入特定于框架的知识的困境,那么这是一个合理的建议。

您会看到,这是使用外观、全局变量或静态方法带来的问题之一。为了使事情正常工作,各种各样的事情都会在您的代码(以及您的测试代码)之外发生。

问题

要了解发生了什么,您首先需要了解 Laravel 如何利用容器工厂将事物粘合在一起。

接下来,发生的事情是:

  1. 你的代码(在某个地方HomeController::home()的调用中view()
  2. view()调用以获取创建视图1app()的工厂
  3. app()来电Container::make
  4. Container::make呼叫Container::resolve1
  5. Container::resolve决定需要建造工厂并呼吁Container::build这样做
  6. 最后Container::build(使用 PHP 的ReflectionClass发现\Illuminate\Contracts\View\Factory无法实例化(因为它是一个接口)并触发您看到的错误。

或者,如果您更像是一个视觉思考者:

在此输入图像描述

触发错误的原因是框架期望配置容器,以便抽象类(例如接口)已知具体类。

解决方案

现在我们知道发生了什么,并且我们想创建一个单元测试,我们能做什么?

一种解决方案可能似乎使用view. 只需自己注入 View 类即可!但如果您尝试这样做,您很快就会发现自己走上了一条基本上在用户空间中重新创建大量框架代码的道路。所以这不是一个好主意。

更好的解决方案是模拟view()现在它实际上是一个单元!)。但这仍然需要仅在测试代码中重新创建框架代码。还是不太好。[3]

最简单的事情就是简单地配置容器并告诉它使用哪个类。此时,您甚至可以模拟 View 类!

现在,纯粹主义者可能会抱怨这还不够“单元”,因为您的测试仍然会在被测代码之外调用“真实”代码,但我不同意......

你正在使用框架,所以使用框架!如果您的代码使用框架提供的粘合,则测试反映此行为是有意义的。只要你不调用非粘合代码,就没有问题![4]

最后,为了让您了解如何做到这一点,举一个例子!

这个例子

假设您有一个看起来有点像这样的控制器:

namespace App\Http\Controllers;

class HomeController extends \Illuminate\Routing\Controller
{
    public function home()
    {
        /* ... */

        return view('my.view');
    }
}
Run Code Online (Sandbox Code Playgroud)

那么你的测试[5]可能看起来像这样:

namespace Tests\Unit\app\Http\Controllers;

use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;

class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
    public function testHome()
    {
        /*/ Arange /*/
        $mockFactory = $this->createMock(Factory::class);

        app()->instance(Factory::class, $mockFactory);

        /*/ Assert /*/
        $mockFactory->expects(self::once())
            ->method('make')
            ->with('my.view')
        ;

        /*/ Act /*/
        (new HomeController())->home();
    }
}
Run Code Online (Sandbox Code Playgroud)

一个更复杂的示例是还创建一个模拟视图并由模拟工厂返回它,但我将把它作为练习留给读者。

脚注

  1. app()要求提供接口Illuminate\Contracts\View\Factory,但没有传递具体的类名
  2. 除了调用另一个函数之外什么也不做的原因Container::make是方法名称make由 PSR-11 定义的,并且 Laravel 容器是 PSR 兼容的。
  3. 此外,Laravel 提供的功能测试逻辑已经为您完成了所有这些...
  4. 只是不要忘记用@uses所需的粘合来注释测试,以避免在 PHPUnit 设置为有关“风险”测试的严格模式时发出警告。
  5. 使用“安排、执行、断言”模式的变体