为了账号安全,请及时绑定邮箱和手机立即绑定

PHPUnit 给出错误:目标 [Illuminate\Contracts\View\] ​​

PHPUnit 给出错误:目标 [Illuminate\Contracts\View\] ​​

PHP
12345678_0001 2023-07-01 17:25:31
我为我的新 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());
查看完整描述

3 回答

?
函数式编程

TA贡献1807条经验 获得超9个赞

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

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

问题

要了解发生了什么,您首先需要了解 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无法实例化(因为它是一个接口)并触发您看到的错误。

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

//img4.sycdn.imooc.com/649ff1320001163106540489.jpg

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

解决方案

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

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

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

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

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

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

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

这个例子

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

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,但没有传递具体的类名

  2. 除了调用另一个函数之外什么也不做的原因Container::make是方法名称make是由 PSR-11 定义的,并且 Laravel 容器是 PSR 兼容的。

  3. 此外,Laravel 提供的功能测试逻辑已经为您完成了所有这些...

  4. 只是不要忘记用@uses所需的粘合来注释测试,以避免在 PHPUnit 设置为有关“风险”测试的严格模式时发出警告。

  5. 使用“安排、执行、断言”模式的变体


查看完整回答
反对 回复 2023-07-01
?
收到一只叮咚

TA贡献1821条经验 获得超4个赞

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

您已经编写的内容可以重写为这样的内容。

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

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


查看完整回答
反对 回复 2023-07-01
?
摇曳的蔷薇

TA贡献1793条经验 获得超6个赞

如果您在《未来》中发现了这一点,并且看到了 @Pothcera 的文字墙,那么您需要了解以下内容:

他这样做的唯一原因以及您在单元测试中首先看到这一点的唯一原因是因为他和您没有在测试文件中从 更改PHPUnit\Framework\TestCase为。Tests\TestCase当您扩展包含app().

我的建议是简单地扩展正确的基础测试用例并继续你的生活。


查看完整回答
反对 回复 2023-07-01
  • 3 回答
  • 0 关注
  • 97 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信