):在 Laravel 测试 setUp() 中复用 Mock 配置的完整指南)
示例工程数据库教程后端【免费下载链接】sql-server-samplesAzure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge项目地址https://gitcode.com/gh_mirrors/sq/sql-server-samples点击查看免费下载导读在单元测试中同一份对象依赖往往被多个测试用例反复使用如果每个测试都从头编写 mock 定义会产生大量重复代码。Mockery 提供的默认期望Default Mock Expectations机制允许你把公共的 mock 配置集中放在测试用例的setup()方法中再通过byDefault()声明把这些期望标记为默认值后续测试既可以沿用默认行为又能在个别用例中按需覆盖或微调。本文将结合本仓库 Laravel 示例项目samples/development-frameworks/laravelvendor 目录中随附的 Mockery 源码与测试完整讲解这一机制的使用方法、底层工作原理与边界约束。一、问题场景测试中重复的 Mock 配置在典型的单元测试中经常会出现这样的情况一组测试用例都依赖同一个协作对象如仓储、邮件发送器、HTTP 客户端等。若在每个测试里都重复书写 mock 创建与期望定义代码量会急剧膨胀且任何公共行为变更都需要改动多处。Mockery 的解法是在测试用例的setup()或setUp()方法中一次性创建带默认期望的 mock供所有测试共享之后在具体测试中再为特定用例添加、覆盖或微调针对该 mock 的期望。官方 Cookbook 文档default_expectations.rst明确指出即便不同测试对同一或相似mock 对象有着不同的期望这种默认期望 局部微调的组合模式依然成立。二、核心概念byDefault()声明默认期望机制的关键是byDefault()声明——任何期望expectation都可以通过调用byDefault()被标记为默认期望。$mock-shouldReceive(findUser) -with(1) -andReturn(new User([id 1])) -byDefault();声明为默认后该期望在没有更具体的期望存在时生效一旦某个测试为同一方法定义了非默认期望后者会立即替换掉先前定义的默认期望。这正对应参考文档中对byDefault()的说明默认期望在未创建非默认期望时被应用后续创建的期望会立即替换先前定义的默认期望见 expectations.rst。三、实战模式在 setUp() 中定义可复用默认 Mock3.1 基础模板以 PHPUnit 测试类为例将公共 mock 配置收敛到setUp()中class OrderServiceTest extends TestCase { protected $orderRepo; protected $mailer; public function setUp() { parent::setUp(); // 在 setUp 中定义默认 mock供本类所有测试复用 $this-orderRepo Mockery::mock(App\Repositories\OrderRepository); $this-orderRepo-shouldReceive(find) -with(Mockery::type(int)) -andReturn(new Order()) -byDefault(); $this-mailer Mockery::mock(App\Mailers\OrderMailer); $this-mailer-shouldReceive(send) -andReturn(true) -byDefault(); } public function tearDown() { Mockery::close(); parent::tearDown(); } }3.2 在具体测试中微调期望默认 mock 建立后个别测试可以通过重新声明期望来实现覆盖或补充public function testSendsEmailForPaidOrders() { // 覆盖默认行为本次测试要求 send 恰好被调用一次 $this-mailer-shouldReceive(send)-once(); $service new OrderService($this-orderRepo, $this-mailer); $service-markPaid(42); // 验证 mock 上的期望含默认期望是否得到满足 $this-mailer-shouldHaveReceived(send)-once(); }注意新增的非默认期望会替换同名方法下已有的默认期望而非叠加因此默认期望适合描述无特殊情况时的兜底行为。四、源码级原理默认期望如何被查找与替换4.1 默认期望的登记makeExpectationDefault每个 mock 方法的期望都由Mockery\ExpectationDirector管理。当调用byDefault()时Expectation::byDefault()Expectation.php会从 mock 上取出该方法的 director并调用其makeExpectationDefault()public function makeExpectationDefault(\Mockery\Expectation $expectation) { $last end($this-_expectations); if ($last $expectation) { array_pop($this-_expectations); array_unshift($this-_defaults, $expectation); } else { throw new \Mockery\Exception( Cannot turn a previously defined expectation into a default ); } }ExpectationDirector.php这段实现透露了两个重要细节只有最新创建的期望才能被转为默认director 仅当该期望位于_expectations数组末尾$last $expectation时才执行转换否则抛出Mockery\Exception。也就是说byDefault()必须紧跟shouldReceive(...)-...链式调用不能对先前已被其他期望覆盖的期望再次声明默认。默认期望被移入独立的_defaults数组默认期望与普通期望分池存放普通期望数组腾空后留给后续测试填充具体期望。4.2 期望查找优先级具体期望优先于默认期望当 mock 方法被实际调用时director 的call()会委托给findExpectation()ExpectationDirector.phppublic function findExpectation(array $args) { if (!empty($this-_expectations)) { return $this-_findExpectationIn($this-_expectations, $args); } else { return $this-_findExpectationIn($this-_defaults, $args); } }可见查找策略是**非默认期望优先默认期望兜底**只要该方法的普通期望列表非空就只在普通期望中匹配匹配规则同时考虑参数matchArgs()与可调用性isEligible()见 ExpectationDirector.php只有普通期望列表为空时才回退到默认期望。这从底层机制上保证了具体测试覆盖默认配置这一行为。4.3 验证阶段默认期望同样参与校验在Mockery::close()或容器mockery_verify()触发验证时director 的verify()ExpectationDirector.php会验证普通期望若普通期望列表为空则验证默认期望。因此默认期望上的once()、twice()等次数约束同样会被强制执行测试可以通过Mockery::close()统一完成验证。五、测试用例印证行为契约一览仓库自带的测试文件 tests/Mockery/ExpectationTest.php 用一组专门测试固化了默认期望的行为契约可作为编写自身测试时的参考范例测试方法验证的行为testExpectationsCanBeMarkedAsDefaults简单默认期望shouldReceive(foo)-andReturn(bar)-byDefault()后调用foo()返回bartestDefaultExpectationsValidatedInCorrectOrder多个默认期望按参数区分with(1)、with(2)各自独立生效并校验testDefaultExpectationsAreReplacedByLaterConcreteExpectations默认期望被后续twice()的具体期望替换默认的once()约束不再生效testDefaultExpectationsCanBeChangedByLaterExpectations默认期望可通过新增期望被改变替换testDefaultExpectationsCanBeOrdered/...AndReplaced默认期望可与ordered()组合使用且替换后仍保持有序验证testByDefaultOperatesFromMockConstruction在mock(f, [...])快速定义后链式调用-byDefault()默认值在 mock 构造时即生效testByDefaultOnAMockDoesSquatWithoutExpectations对无期望的 mock 调用byDefault()无副作用testDefaultExpectationsCanBeOverridden两个先后byDefault()的期望后者生效后定义覆盖先定义testByDefaultPreventedFromSettingDefaultWhenDefaultingExpectationWasReplaced已被替换的旧期望再调用byDefault()会抛出Mockery\Exception这些测试与 ExpectationDirector.php 的实现互相印证可以直接作为默认期望机制的可执行文档。六、进阶用法与注意事项6.1 对整组快速定义批量设默认Mockery\Mock提供了 mock 级别的byDefault()Mock.php可一次性把该 mock 上所有已定义的期望批量标记为默认$mock $container-mock(f, [foo rfoo, bar rbar])-byDefault();容器在快速定义场景下也会自动应用默认标记见 Container.php。测试testByDefaultOperatesFromMockConstruction验证了这种写法的效果默认值立即生效且之后新增的shouldReceive(foo)-andReturn(foobar)会优先于默认值。6.2 与 ordered() 组合默认期望同样可以参与调用顺序约束shouldReceive(foo)-ordered()-byDefault()。由于默认期望与具体期望采用同一套 order 编号机制Expectation::ordered()见 Expectation.php在setUp()中声明的有序默认期望也能在具体测试中被有序的新期望替换而不破坏顺序约束参见上述测试testDefaultExpectationsCanBeOrderedAndReplaced。6.3 注意事项byDefault()必须紧跟最新创建的期望它只对当前最后一条期望生效对已被覆盖的旧期望调用会抛异常。默认期望是兜底而非叠加新增具体期望会整体替换默认期望若需多个分支行为请分别用不同参数匹配with(...)声明多个默认期望。别忘了Mockery::close()在tearDown()中调用以统一验证所有期望含默认期望并清理容器状态。七、总结默认期望byDefault()是 Mockery 中性价比极高的测试组织手段它把每个测试都要重复的 mock 配置下沉到setUp()通过默认期望 局部覆盖让每个测试只保留自己关心的差异点。从源码看这一机制由ExpectationDirector的普通期望 / 默认期望双列表与普通优先、默认兜底的查找策略共同支撑行为契约则由 ExpectationTest.php 中的专项测试固化。读者可在本仓库的 Mockery 文档目录 中继续阅读 期望参考文档 与 快速示例 获取更多细节。赞分享示例工程数据库教程后端【免费下载链接】sql-server-samplesAzure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge项目地址https://gitcode.com/gh_mirrors/sq/sql-server-samples点击查看免费下载相关推荐Mockery 默认期望Default Mock Expectations用 byDefault() 消灭重复的 Mock 配置Mockery 默认期望Default Mock Expectations用 byDefault 消灭重复的 Mock 配置 导读 在 PHP 单元测试中测试开发工具PHPUnit 集成 Mockery在 Laravel 测试套件中优雅地完成 Mock 清理与期望验证PHPUnit 集成 Mockery在 Laravel 测试套件中优雅地完成 Mock 清理与期望验证 Mockery 是一款以独立、即插即用为设计理念的示例工程数据库教程后端在 miniblink49 测试体系中用好 Google C Mock从 Mock 类定义到期望设置的完整实战指南在 miniblink49 测试体系中用好 Google C Mock从 Mock 类定义到期望设置的完整实战指南 本文基于 miniblink49 仓库前端桌面应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考