如何在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使用,实现逻辑如下:
- 在Trait或者测试基类中定义Meilisearch初始化方法,创建一个内存级的Fake客户端对象,替代容器中原本的真实
Meilisearch\Client实例。 - Fake客户端内部维护调用记录,模拟
index()、addDocuments()、deleteDocument()、getDocument()等常用方法的行为,不发起任何真实HTTP请求,所有操作都在内存数组中完成。 - 配套实现常用断言方法,直接从内存记录中校验索引操作是否符合预期,不需要等待索引同步,测试结束后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
相关产品推荐
相关产品推荐

