【问题标题】:Laravel unit testing automatic dependency injection not working?Laravel 单元测试自动依赖注入不起作用?
【发布时间】:2020-06-15 06:23:03
【问题描述】:

使用 Laravel Framework 5.8.36,我正在尝试运行一个调用控制器的测试,其中 __construct 方法使用 DI,如下所示:

class SomeController extends Controller {

public function __construct(XYZRepository $xyz_repository)
{
    $this->xyz_repository = $xyz_repository;
}

public function doThisOtherThing(Request $request, $id)
{
    try {
        return response()->json($this->xyz_repository->doTheRepoThing($id), 200);
    } catch (Exception $exception) {
        return response($exception->getMessage(), 500);
    }
}
}

如果我通过浏览器运行代码或像邮递员中的 api 调用一样调用它,这工作正常,但是当我从测试中调用 doThisOtherThing 方法时,我收到以下错误:

ArgumentCountError:函数 App\Http\Controllers\SomeController::__construct() 的参数太少,在第 28 行的 /var/www/tests/Unit/Controllers/SomeControllerTest.php 中传递了 0,而预期正好是 1

这告诉我,当我运行测试时,DI 出于某种原因无法正常工作。有任何想法吗?这是我的测试:

public function testXYZShouldDoTheThing()
{
    $some_controller = new SomeController();
    $some_controller->doThisOtherThing(...args...);
    ...asserts...
}

我已经尝试过在 setUp 方法中使用 app 上的 bind 和 make 方法,但没有成功:

public function setUp(): void
{
    parent::setUp();
    $this->app->make('App\Repositories\XYZRepository');
}

【问题讨论】:

    标签: laravel dependency-injection phpunit


    【解决方案1】:

    这不是 Laravel 服务容器的工作方式,当使用 new 关键字时,它永远不会通过容器解析,因此 Laravel 无法注入所需的类,您必须自己传递它们才能使其像这样工作。

    可以做的是让控制器通过服务容器来解析:

    public function testXYZShouldDoTheThing()
    {
        $controller = $this->app->make(SomeController::class);
        // Or use the global resolve helper
        $controller = resolve(SomeController::class);
    
        $some_controller->doThisOtherThing(...args...);
        ...asserts...
    }
    

    来自docs

    您可以使用 make 方法从 容器。 make 方法接受类或接口的名称 你想解决:

    $api = $this->app->make('HelpSpot\API');
    

    如果您的代码所在位置无法访问 $app 变量,你可以使用全局解析助手:

    $api = resolve('HelpSpot\API');
    

    PS:

    我不太喜欢像您在这里尝试做的那样测试控制器,我宁愿创建一个 feature test 并测试路由并验证一切是否按预期工作。

    功能测试可能会测试大部分代码,包括如何 多个对象相互交互,甚至是一个完整的 HTTP 请求 到 JSON 端点。

    类似这样的:

    use Illuminate\Http\Response;
    
    public function testXYZShouldDoTheThing()
    {
        $this->get('your/route')
            ->assertStatus(Response::HTTP_OK);
            // assert response content is correct (assertJson etc.)
    }
    

    【讨论】:

    • 我 100% 同意控制器的测试。我应该使用功能测试来测试控制器。我会更新测试来做到这一点。
    • 功能测试不是单元测试的一般替代品。这对控制器来说可能很好,但不应该代替整个单元测试。
    【解决方案2】:

    没错。单元测试的整个想法是模拟依赖服务,以便您可以一致地控制它们的输入/输出。

    您可以创建 XYZRepository 的模拟版本并将其注入到您的控制器中。

    $xyzRepositoryMock = $this->createMock(XYZRepository::class);
    $some_controller = new SomeController($xyzRepositoryMock);
    $some_controller->doThisOtherThing(...args...);
    

    【讨论】:

    • 所以自动注入不适用于测试? laravel.com/docs/5.8/container#automatic-injection我不想为这个存储库创建一个模拟并实际将它传递给 SomeController __construct 方法。
    • 你如何处理这个错误:类“SomeOtherClass”被声明为“final”并且不能被模拟
    • 您当然可以创建存储库的真实实例,但您必须在没有自动注入的情况下完成所有操作(这意味着您最终可能会创建大量对象,而这实际上不是您想要的'正在寻找。如果你真的想完整地测试控制器,也许是你在集成测试中寻找的东西。你可以调用你自己的端点,让控制器以这种方式运行它的自然过程。
    • 测试final类真的很烦人。您可以查看该课程并决定它是否真的必须是最终的。如果应该,那么您可以在这里找到一些方法:tomasvotruba.com/blog/2019/03/28/…
    • 感谢 Dirk,最后一个类是第三方 api 插件。我想我不会在该课程中使用 DI。
    猜你喜欢
    • 2017-04-01
    • 2018-01-11
    • 2015-11-30
    • 2021-06-19
    • 1970-01-01
    • 2021-05-29
    • 2018-03-03
    相关资源
    最近更新 更多