You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PHPUnit中Mock Meilisearch优化Laravel单元测试

Laravel中Mock Meilisearch索引的实现方案

问题背景

在Laravel应用中集成Meilisearch时,当前测试逻辑较为基础,索引创建测试代码示例如下:

public function test_create_index()
{
    $model = Package::factory()->create([
        'language' => 'DA'
    ]);
    sleep(1);

    $this->assertIsArray($this->meiliClient->index('Package_DA')->getDocument($model->id));

    $this->meiliClient->index('Package_DA')->deleteDocument($model->id);
}

现有逻辑通过Laravel Observer监听新模型创建事件,触发对应方法调用自定义的Meilisearch Service完成索引创建与记录写入,但测试方案存在两个明显缺陷:

  • 执行效率极低:Meilisearch索引数据写入存在同步延迟,每次调用Meilisearch客户端后必须添加sleep(1)等待数据生效,单测试文件共有8处相关调用,总测试耗时超过8秒。
  • 维护成本高:测试执行完成后需要手动调用接口删除测试产生的索引记录,清理逻辑重复冗余。

预期实现效果和Laravel自带的Event、Job Fake能力一致:无需真实请求Meilisearch生成数据,仅验证相关调用逻辑是否执行正确。考虑过通过独立Trait实现该能力,但不确定是否能达到预期的索引伪造效果。


可行实现方案

方案1:容器绑定Mock客户端(推荐,和Laravel原生Fake逻辑一致)

不需要依赖额外扩展,利用Laravel服务容器的实例替换能力,直接拦截所有Meilisearch真实请求,完全可以封装成独立Trait使用,实现逻辑如下:

  1. 在Trait或者测试基类中定义Meilisearch初始化方法,创建一个内存级的Fake客户端对象,替代容器中原本的真实Meilisearch\Client实例。
  2. Fake客户端内部维护调用记录,模拟index()、addDocuments()、deleteDocument()、getDocument()等常用方法的行为,不发起任何真实HTTP请求,所有操作都在内存数组中完成。
  3. 配套实现常用断言方法,直接从内存记录中校验索引操作是否符合预期,不需要等待索引同步,测试结束后Fake对象随测试生命周期自动销毁,无需手动清理测试数据。

核心实现代码参考:

// 可封装为InteractsWithMeilisearch Trait
protected function fakeMeilisearch()
{
    $fakeClient = new class {
        public array $indexes = [];

        public function index(string $name): object
        {
            if (!isset($this->indexes[$name])) {
                $this->indexes[$name] = new class {
                    public array $addedDocuments = [];
                    public array $deletedIds = [];

                    public function addDocuments(array $documents): void
                    {
                        $this->addedDocuments = array_merge($this->addedDocuments, $documents);
                    }

                    public function deleteDocument(mixed $id): void
                    {
                        $this->deletedIds[] = $id;
                    }

                    public function getDocument(mixed $id): array
                    {
                        return collect($this->addedDocuments)->firstWhere('id', $id) ?? [];
                    }
                };
            }
            return $this->indexes[$name];
        }
    };

    // 绑定到容器,替换真实客户端实例
    $this->app->instance(\Meilisearch\Client::class, $fakeClient);
    // 如果自定义了Meilisearch Service,同步替换服务中依赖的客户端即可
    return $fakeClient;
}

测试中使用方式如下,完全不需要sleep和手动清理:

public function test_create_index()
{
    $fake = $this->fakeMeilisearch();
    $model = Package::factory()->create([
        'language' => 'DA'
    ]);

    $indexDoc = collect($fake->index('Package_DA')->addedDocuments)
        ->firstWhere('id', $model->id);
    $this->assertIsArray($indexDoc);
}

如果你是基于Laravel Scout对接的Meilisearch,不需要自己写上述逻辑,直接调用Scout官方提供的Search::fake()方法即可,自带assertSearchable、assertNotSearchable等断言方法,完全满足调用逻辑校验需求。

方案2:临时测试实例(适合需要验证搜索逻辑的场景)

如果测试不仅需要校验调用是否发起,还要验证搜索过滤、排序、分词等实际搜索逻辑,可以使用Meilisearch临时实例方案:

  • 测试启动时自动启动一个内存运行的临时Meilisearch实例,每个测试执行结束后自动销毁实例,不需要手动清理测试数据。
  • 所有索引写入操作后,调用客户端自带的waitForPendingUpdate()方法阻塞等待索引更新完成,替代硬编码的sleep(1),实际等待时间通常仅几十毫秒,测试执行效率远高于固定等待方案。

内容的提问来源于stack exchange,提问作者Oskar Mikael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 03:51:26