繁体   English   中英

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

[英]PHPUnit gives error: Target [Illuminate\Contracts\View\Factory] is not instantiable

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

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

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

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

这不是您在Laravel中测试端点的方式。 您应该让Laravel实例化应用程序,因为它已经在项目中设置,您可以在此处查看示例。

你已经写的东西可以重写成这样的东西。

$response = $this->call('GET', route('home')); // insert the correct route

$response->assertOk(); // should be 200

为了使测试正常工作,您应该扩展位于您的测试文件夹中的TestCase.php

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

你看,这是使用外观、全局变量或 static 方法带来的问题之一。 各种各样的事情都发生在您的代码(以及您的测试代码)之外,以使事情正常进行。

问题

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

接下来,发生的事情是:

  1. 您的代码(在HomeController::home()中调用view()某处。
  2. view()调用app()获取创建 Views 的工厂1
  3. app()调用Container::make
  4. Container::make调用Container::resolve 1
  5. Container::resolve决定需要构建工厂并调用Container::build来执行此操作
  6. 最后Container::build (使用 PHP 的ReflectionClass发现\Illuminate\Contracts\View\Factory不能被实例化(因为它是一个接口)并触发您看到的错误。

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

在此处输入图像描述

触发错误的原因是框架期望容器被配置,以便一个具体的 class 被称为抽象(例如接口)。

解决方案

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

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

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

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

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

您正在使用框架,因此请使用框架,如果您的代码使用框架提供的胶水。 测试反映这种行为是有意义的,只要你不调用非胶水代码,你会没事的! [4]

所以,最后,给你一个关于如何做到这一点的想法,一个例子!

这个例子

假设您有一个 controller,看起来有点像这样:

namespace App\Http\Controllers;

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

        return view('my.view');
    }
}

那么你的测试[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();
    }
}

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

脚注

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM