【问题标题】:Laravel8 - Mocking methods from factory relationship modelsLaravel 8 - 来自工厂关系模型的模拟方法
【发布时间】:2021-11-06 19:25:09
【问题描述】:

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

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

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

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

是否可以使用 Post 模型类的 Mock,所以我可以制作,例如:

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

所以我不需要创建一个完整的场景来进行测试?

可能的解决方案:

扩展 Post 类,仅用于测试:

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();

【问题讨论】:

  • 你能包含 canAddComment() 吗,它看起来像是静态的,没有模拟静态函数的好方法。通常我不会关心 canAddComment 的副作用并断言你正在测试什么,什么是破坏?如果是外部 api 等,为什么不只是模拟一下
  • 它不是静态的。我测试的内容取决于这个函数的结果。当然,这与帖子和评论无关,但它是一个验证功能,不使用外部 API,但需要另外 5 个工厂来创建正确的场景。我最终创建了这个场景并且测试正在运行,但是测试变得太复杂了,并且验证功能本身已经在它自己的测试中进行了测试......如果没有其他更好的选择,也许我应该尝试将这段代码分解成更小的可测试的部分..
  • 有一些选项,但我需要一些示例代码来提出如何做的建议:P Post::canAddComment() 是一个静态调用 btw
  • 我的错,我没有写正确..我只是想写 canAddComment() 是 Post 类中的一个方法:D
  • 我不允许在这里放真正的代码,对不起……但是如果这有帮助,一切都在同一个类中完成(示例中的 Post),一切都是 SQL 和业务逻辑。我刚刚想到的一个想法是创建一个扩展模型 Post 的类,更改我需要的内容,并为它创建一个工厂。这种方法的缺点是最终会在要测试的代码库中使用测试类......

标签: php laravel unit-testing mocking mockery


【解决方案1】:

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

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
    }
}

【讨论】:

    猜你喜欢
    • 2018-06-13
    • 2015-12-09
    • 2017-03-21
    • 2020-12-28
    • 2021-01-26
    • 2021-11-21
    • 2020-03-09
    • 2021-01-04
    • 2020-12-28
    相关资源
    最近更新 更多