在 PHPUnit 中调用路由时如何在 Laravel 8 中模拟 Eloquent 模型

Posted

技术标签:

【中文标题】在 PHPUnit 中调用路由时如何在 Laravel 8 中模拟 Eloquent 模型【英文标题】:How to mock Eloquent Model in Laravel 8 when calling a route in PHPUnit 【发布时间】:2021-09-14 04:40:25 【问题描述】:

我正在尝试在模型中模拟一个方法,以便能够在不同的场景下测试控制器 api 端点。

我目前正在使用带有 phpUnit 和 Mockery 包的 Laravel 8。 在路由中我使用的是Route模型绑定。

api.php

Route::get('/api/project', [ProjectController::class, 'show']);

ProjectController.php

class ProjectController extends Controller 

    public function show(Project $project)
    
        $state = $project->getState();
        
        return response()->json($state);
    

ProjectControllerTest.php

class ProjectControllerTest extends TestCase

    /**
     * @test
     * @group ProjectController
     */
    public function shouldReturn200ForValidProjectGuid()
    
        $project = Project::factory()->create();

        $mock = Mockery::mock(Project::class);

        // I have also tried this, see error below
        // $mock = Mockery::mock(Project::class)->makePartial();

        $this->app->instance(Project::class, $mock);

        $mock->shouldReceive('getState')->andReturn('New South Wales');

        $response = $this->getJson("/api/$project->guid");
        
        $response->assertStatus(200)
            ->assertJson([
                'data' => 'New South Wales'
            ]);
     


我目前收到此错误

Received Mockery_0_App_Models_Project::resolveRouteBinding(), but no expectations were specified Exception caught at [/usr/src/app/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php(34) : eval()'d code@924

我已经尝试了其他选项,例如 makePartial(),但是它也会导致以下错误

Received Mockery_0_App_Models_Project::__construct(), but no expectations were specified Exception caught at [/usr/src/app/vendor/mockery/mockery/library/Mockery/Loader/EvalLoader.php(34) : eval()'d code@924

任何帮助将不胜感激,谢谢

【问题讨论】:

为什么不使用标准模拟 phpunit $stub = $this->createMock(SomeClass::class) ? @АлександрЧерножуков 我试过用它,但还是不行,你能提供一个例子吗 我读到了在mock上调用resolveRouteBinding方法的错误信息,但是mock不知道如何处理这个调用,因此抛出了异常。在让系统使用模拟之前,实际指定这个缺失的期望会有所帮助。 @hakre 你能提供一个例子吗 【参考方案1】:

您可以尝试以下方法。我稍微更改了控制器响应以使其与assertJson() 一起使用。

web.php

Route::get('/projects/project', [ProjectController::class, 'show']);

项目控制器

<?php

namespace App\Http\Controllers;

use App\Models\Project;

class ProjectController extends Controller

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show(Project $project)
    
        return response()->json([
            'state' => $project->getState()
        ]);
    

ProjectControllerTest

<?php

namespace Tests\Feature;

use App\Models\Project;
use Mockery;
use Tests\TestCase;

class ProjectControllerTest extends TestCase

    /**
     * A basic test example.
     *
     * @return void
     */
    public function test_example()
       
        $mock = Mockery::mock(Project::class)->makePartial();

        $mock->shouldReceive('resolveRouteBinding')->andReturnSelf();
        $mock->shouldReceive('getState')->andReturn('NSW');

        $this->app->instance(Project::class, $mock);

        $response = $this->get('/projects/1');

        $response->assertStatus(200)
            ->assertJson([
                'state' => 'NSW'
            ]);
    

【讨论】:

这不是一个有效的答案,因为您没有模拟模型......但无论如何......【参考方案2】:

你没有模拟模型,你已经在使用Factory,所以你仍然使用它在你的模型上创建“模拟”数据。

$project = Project::factory()->create(['state' => 'New South Wales']);

阅读有关 Laravel 中的工厂和单元测试的更多信息。请参阅官方文档。

【讨论】:

状态不是项目模型的一部分,这是模型中调用的外部数据库,我只想模拟该方法以返回不同的东西。否则我会改用工厂 好吧,所以你还是不模拟一个模型,你模拟模型里面的东西,你能显示模型代码吗?

以上是关于在 PHPUnit 中调用路由时如何在 Laravel 8 中模拟 Eloquent 模型的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Laravel 中的 PHPUnit 不能访问存在的路由?

如何在 laravel 5.5 中使用 phpunit 测试中间件?

Laravel 路由仅在 phpunit 测试期间返回 302 而不是 404 响应

看不到响应获取请求 laravel phpunit

如何创建仅用于测试的 Laravel 路由(phpunit)?

为啥当我运行我的 phpunit 测试时,我得到一个空响应,但 Postman 中的相同路由/用户的行为符合预期?