尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Coolify 测试实践:用 laravel-actions 的 AsFake 隔离 Action 编排并做分支断言

Coolify 测试实践:用 laravel-actions 的 AsFake 隔离 Action 编排并做分支断言 Coolify 测试实践用 laravel-actions 的 AsFake 隔离 Action 编排并做分支断言【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku Netlify that lets you easily deploy static sites, databases, full-stack applications and 280 one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify本文围绕 Coolify 仓库中的测试参考文档 .claude/skills/laravel-actions/references/testing-fakes.md 展开系统讲解lorisleiva/laravel-actions2.x 提供的AsFake全套测试辅助方法mock、partialMock、spy、shouldRun、shouldNotRun、allowToRun、isFake、clearFake说明何时断言执行、何时断言不执行的决策原则并结合 Coolify 源码中 57 个 Action 与 41 个实际测试文件展示如何在 Pest 测试中用 fake 隔离 action 编排、防止 fake 跨测试泄漏。读完后你能够在自己的 Laravel 项目中复现同样的测试模式。背景为什么 Coolify 需要 Action 级 fakeCoolify 是一个可自托管的 PaaS开源替代 Vercel/Heroku/Netlify其业务逻辑大量组织在 app/Actions 目录下的 Action 类中——代理启停app/Actions/Proxy、服务部署app/Actions/Service、数据库操作app/Actions/Database等仓库中共有 57 个使用AsActiontrait 的类包版本在 composer.json 中声明为lorisleiva/laravel-actions: ^2.10.2composer.lock 锁定到 v2.10.2。Action 的价值在于同一个用例use-case可以被多种入口HTTP 路由、队列 Job、事件监听器、Artisan 命令调用核心业务逻辑只写一份handle(...)。但这也带来了测试问题当你测试一个上游编排逻辑例如导入团队联系人时不希望真的去连 Google API而只希望证明在正确分支下下游 Action 被以正确的参数调用了。AsFake提供的正是这种类级别的静态替换机制用法类似 Laravel 原生的Queue::fake()/Event::fake()但对象是 Action 类本身。参考文档给出了三条推荐模式Recommended pattern也是本文的组织线索直接测试handle(...)验证业务规则本身测试入口点entrypoint验证接线与编排wiring/orchestration只在被测边界处打 fakeFake only at the boundary under test不要处处 mock。AsFake 核心方法逐个拆解以下 8 个方法全部继承自参考文档的核心清单每个方法先给出语义再给出文档中的原始示例。mock()全量替换 严格期望mock()用一个完整的 Mockery mock 替换该 Action 类。适合需要严格期望strict expectation与参数断言的场景——未满足的期望会在测试结束时使用例失败。FetchContactsFromGoogle::mock() -shouldReceive(handle) -with(42) -andReturn([Loris, Will, Barney]);等价于手动写MyAction::mock()-shouldReceive(handle)-once()-with(42)-andReturnTrue()。注意期望链是可继续拼接的once()、twice()、withAnyArgs()等 Mockery 方法都能用这让mock()成为表达交互契约最精确的工具。partialMock()保留真实行为只桩化一个内部方法partialMock()替换为部分 mock类实例本身是真实的只有你声明了期望的方法被桩化。适合大部分行为保留、只替换某个昂贵或外部依赖方法的场景。FetchContactsFromGoogle::partialMock() -shouldReceive(fetch) -with(some_google_identifier) -andReturn([Loris, Will, Barney]);例如handle()内部调用了fetch()去访问 Google测试时让fetch()直接返回固定数组而handle()里围绕fetch()的编排、校验、落库逻辑仍然真实执行。spy()先放行、后验证spy()把 Action 换成 spy调用会被记录但行为可控默认拒绝需显式allows(...)。典型流程是先执行、后断言post-execution verification不需要在测试开头预定义所有期望。$spy FetchContactsFromGoogle::spy() -allows(handle) -andReturn([Loris, Will, Barney]); // ... 执行触发该 Action 的代码 ... $spy-shouldHaveReceived(handle)-with(42);shouldRun() / shouldNotRun()分支测试的语法糖这两个助手是文档反复强调的分支导向测试branch-oriented test核心shouldRun()等价于FetchContactsFromGoogle::mock()-shouldReceive(handle);——断言这条分支下 Action必须被执行shouldNotRun()等价于FetchContactsFromGoogle::mock()-shouldNotReceive(handle);——断言这条分支下 Action绝不能被执行守卫子句测试。FetchContactsFromGoogle::shouldRun(); // Equivalent to: FetchContactsFromGoogle::mock()-shouldReceive(handle); FetchContactsFromGoogle::shouldNotRun(); // Equivalent to: FetchContactsFromGoogle::mock()-shouldNotReceive(handle);正/负期望成对覆盖一个 if 分支是让编排分支具备回归保护的关键手段。allowToRun()spy 的便捷版allowToRun()相当于创建 spy 允许handle通过执行会继续同时保留交互断言能力$spy FetchContactsFromGoogle::allowToRun() -andReturn([Loris, Will, Barney]); // ... $spy-shouldHaveReceived(handle)-with(42);参考文档 SKILL.md 中的实用默认值Practical defaults给出了选型口诀分支可读性优先用shouldRun()/shouldNotRun()行为基本真实、只需验证调用用spy()/allowToRun()交互契约严格、要快速失败用mock()。isFake() / clearFake()fake 生命周期检查与重置这是文档中专门覆盖 fake 生命周期的一对方法也是防止测试间污染的安全网FetchContactsFromGoogle::isFake(); // false FetchContactsFromGoogle::mock(); FetchContactsFromGoogle::isFake(); // true FetchContactsFromGoogle::mock(); FetchContactsFromGoogle::isFake(); // true FetchContactsFromGoogle::clearFake(); FetchContactsFromGoogle::isFake(); // falseisFake()返回该 Action 当前是否已被 fake 替换clearFake()清除已设置的 fake 实例若存在。文档示例编排测试与守卫子句测试参考文档给出了两个 Pest 风格的完整示例分别对应断言执行与断言不执行两类分支这是shouldRun链式用法的最小可运行模板编排测试正向分支it(runs sync contacts for premium teams, function () { SyncGoogleContacts::shouldRun()-once()-with(42)-andReturnTrue(); ImportTeamContacts::run(42, isPremium: true); });shouldRun()-once()-with(42)-andReturnTrue()一次性表达了四件事下游 Action 必须执行、恰好一次、参数为42、返回true。被测对象ImportTeamContacts::run(...)走真实编排逻辑只有它调用的下游被替换。守卫子句测试负向分支it(does not run sync when integration is disabled, function () { SyncGoogleContacts::shouldNotRun(); ImportTeamContacts::run(42, integrationEnabled: false); });如果哪天有人在集成已禁用分支里错误地调用了SyncGoogleContacts这个用例会因shouldNotReceive违例而失败——负向断言同样能锁住回归。在 Coolify 仓库中的真实落地文档中的示例是教学用的Coolify 测试套件Pest见 tests/Pest.php 与 tests/TestCase.php中已有 41 个文件在使用这组方法。以下三个用例展示了文档模式在项目中的真实形态核心落点正是clearFake()的生命周期管理。用 clearFake() 防止 fake 泄漏到下一个用例tests/Feature/CheckDomainDnsJobTest.php 在文件头部一行注册了全局清理钩子use App\Actions\Shared\CheckDomainDns; afterEach(fn () CheckDomainDns::clearFake());由于AsFake的替换发生在类级别静态绑定如果一个用例中mock()过CheckDomainDns而未清除后续用例可能命中上一次的期望配置产生难以定位的偶发失败。文档 Checklist 里的一条——Fakes are cleared when leakage risk exists——在这个文件里就是无条件afterEach清除的落地形式。tests/Feature/CrossTeamDestinationAttachTest.php 采用同一模式afterEach中调用GetContainersStatus::clearFake()配合Queue::fake()与RefreshDatabase测试跨团队挂载服务器的越权路径。被测对象是 Action 本身时fake 住外部依赖而非 Actiontests/Feature/Subscription/SyncStripeSubscriptionsActionTest.php 是一个两层策略的典型测试目标SyncStripeSubscriptions是 app/Actions/Stripe/SyncStripeSubscriptions.php属于直接测试handle(...)业务规则的第一层。此时不打 fake 的边界是 Stripe SDK而是用 Mockery 直接桩化StripeClient/SubscriptionService$stripe Mockery::mock(StripeClient::class); $subscriptions Mockery::mock(SubscriptionService::class); $stripe-subscriptions $subscriptions; $subscriptions-shouldReceive(all)-twice()-andReturn(stripeSubscriptionCollection()); $subscriptions-shouldReceive(retrieve)-with(sub_stale)-andReturn((object) [ status unpaid, customer cus_stale, ]);该文件同样在afterEach中调用SyncStripeSubscriptions::clearFake()与前述用例保持一致的生命周期纪律。两层策略在编排链路中如何组合以 app/Actions/Proxy/CheckProxy.php 为例可以看到两层策略的分工handle(Server $server, $fromUI false): bool内是纯业务判断服务器是否可用、是否构建服务器、代理类型、端口冲突等并在内部真实调用另一个 ActionGetProxyConfiguration::run($server)。测试时第一层业务规则直接构造Server模型/工厂数据调用CheckProxy::run(...)或new CheckProxy(...)-handle(...)验证各种 guard 分支返回true/false第二层入口与编排在测试调用它的 Job/Controller/Livewire 组件时用CheckProxy::shouldRun()/CheckProxy::shouldNotRun()或shouldRun()-with($server)断言下游是否被正确触发、参数契约是否成立。这正是文档 Recommended pattern 中Testhandle(...)directly for business rules. Test entrypoints for wiring/orchestration.在 Coolify 中的直接映射。检查清单与常见陷阱参考文档给出的 Checklist可直接作为 code review 要点断言要验证调用意图与参数契约call intent and argument contracts即with(...)不能省存在泄漏风险时必须清理 fakeclearFake()分支测试优先使用shouldRun()/shouldNotRun()语义比裸 mock 更直白。文档同时列出了两条 Common pitfalls过度 mockover-mockingfake 得太多测试只剩我调用了谁丢失了对真实行为的信心——对应前文只在被测边界打 fake的原则只断言 dispatch、不断言业务正确性入口层测试证明了调用了SyncGoogleContacts但如果SyncGoogleContacts::handle()本身错了测试照样全绿——必须保留第一层的handle(...)直测来兜底。小结AsFake把 Laravel 生态中Queue::fake()式的边界隔离思想推广到 Action 类mock()/shouldRun()表达必须发生shouldNotRun()表达绝不能发生spy()/allowToRun()表达放行并记录partialMock()表达只换一个方法isFake()/clearFake()负责生命周期卫生。Coolify 的 composer.json 中lorisleiva/laravel-actions ^2.10.2依赖、app/Actions 下 57 个 Action 类以及 tests/Feature、tests/Unit 中 41 个实际使用这些方法的测试文件如 tests/Feature/CheckDomainDnsJobTest.php、tests/Feature/CrossTeamDestinationAttachTest.php、tests/Feature/Subscription/SyncStripeSubscriptionsActionTest.php构成了这套模式的完整证据链。掌握两层测试策略 边界处打 fake afterEach 清理这三个要点即可在任何基于AsAction的 Laravel 项目中复现同样的编排测试能力。【免费下载链接】coolifyAn open-source, self-hostable PaaS alternative to Vercel, Heroku Netlify that lets you easily deploy static sites, databases, full-stack applications and 280 one-click services on your own servers.项目地址: https://gitcode.com/GitHub_Trending/co/coolify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表