Laravel8 - 来自工厂关系模型的模拟方法

Laravel8 - Mocking methods from factory relationship models

我正在尝试测试我的代码的特定部分,但是模型 class 的某些依赖项使得此测试很难执行。如果这些依赖关系是在注入的 class 上,我可以轻松地模拟 class。当它来自工厂的数据库关系时,是否可以做类似的事情?我正在将想法转换为 Laravel 文档中的示例,以尝试使其更容易举例:

use App\Models\Post;
use App\Models\User;

$user = User::factory()
            ->has(Post::factory()->count(3))
            ->create();

假设我正在测试一个将为 post 用户创建评论的控制器。我将使用一个工厂来创建用户和 post 并对路由 /api/comments 执行测试 post 调用,此 post 具有字段 post_id( int)和评论(文本)。 但是,评论控制器将调用方法 Post::canAddComment(),该方法将执行大量验证以验证是否可以创建评论。所有这些验证都超出了我的测试范围。

是否可以使用 Post 模型 class 的 Mock,这样我就可以制作,例如:

$postMock->shouldReceive('canAddComment')->once()->andReturn(true);

这样我就不需要创建一个完整的场景就可以进行测试?

可能的解决方案:

扩展 Post class,仅供测试:

class PostThatAlwaysAllowsComments extends Post
{
    public function canAddComment() {
        return true;
    }
}

然后在测试中:

use App\Models\Post;
use App\Models\User;

$user = User::factory()
            ->has(PostThatAlwaysAllowsComments::factory()->count(3))
            ->create();

Laravel中,mocking models并不是一个真正的标准,也不方便。大多数时候,你安排数据,让模型做模型做的事。在您的情况下,您可以使用包装器/代理方法,以便更轻松地模拟您的呼叫。这也将作为模拟的入口点,因为您需要在要模拟的容器中有一个 class。

class PostThatAlwaysAllowsComments extends Post
{
    public function canAddComment() {
        resolve(CommentService::class)->comment($this);
    }
}

使用 resolve as 从容器中获取服务,因为 new 关键字不起作用。

现在使用 Laravels 模拟语法糖,您可以模拟底层服务并避免触发您不关心的代码并断言它实际被调用。

namespace Tests\Unit;

use App\Services\CommentService;
use Mockery\MockInterface;
use Tests\TestCase;

class PostAddComentTest extends TestCase
{
    public function testDoWork()
    {
        $post = // create your model with the factory;

        $mock = $this->mock(CommentService::class, function (MockInterface $mock) {
            $mock->shouldReceive('comment')
                ->once()
                ->andReturn(true);
        });

        // call api or method
    }
}