Mockery expectations API详解:构建复杂测试场景

Mockery expectations API详解:构建复杂测试场景

【免费下载链接】mockery Mockery是一个针对PHP的轻量级模拟对象框架,专为进行依赖注入和隔离测试设计,通过创建模拟对象替代真实依赖,使得单元测试更便捷、高效。 【免费下载链接】mockery 项目地址: https://gitcode.com/gh_mirrors/mo/mockery

你是否在编写单元测试时,遇到过需要验证某个方法被调用的次数、参数是否符合预期,或者需要模拟不同返回值的情况?Mockery的Expectations API正是为解决这些问题而生。本文将带你深入了解如何使用Expectations API构建复杂的测试场景,让你的单元测试更加精准和高效。

读完本文后,你将能够:

  • 掌握Mockery Expectations API的核心方法
  • 构建带有参数验证的测试场景
  • 设置复杂的返回值和异常抛出规则
  • 验证方法调用次数和顺序
  • 解决常见的测试难题

Expectations API基础

Mockery的Expectations API允许你为模拟对象设置预期行为,包括方法调用、参数验证、返回值和调用次数等。核心入口是shouldReceive()方法,用于声明对某个方法的期望。

$mock = \Mockery::mock('MyClass');
$mock->shouldReceive('name_of_method');

这条简单的代码声明了我们期望MyClassname_of_method方法会被调用。但通常我们需要更具体的期望,比如指定参数、返回值和调用次数。

核心API概览

方法作用
shouldReceive()声明对方法的期望
shouldNotReceive()声明方法不应被调用
with()指定方法参数 expectations
andReturn()设置返回值
once()/twice()/times(n)指定调用次数
andThrow()设置抛出异常

完整的API文档可参考官方文档

方法调用与参数验证

基本参数匹配

最常用的参数验证方式是使用with()方法,它可以精确匹配方法调用时的参数:

$mock->shouldReceive('foo')
     ->with('Hello');

$mock->foo('Hello'); // 匹配预期
$mock->foo('Goodbye'); // 抛出NoMatchingExpectationException

灵活的参数匹配器

除了精确匹配,Mockery还提供了多种灵活的参数匹配器:

// 匹配任何参数
$mock->shouldReceive('foo')->withAnyArgs();

// 不接受任何参数
$mock->shouldReceive('foo')->withNoArgs();

// 部分参数匹配
$mock->shouldReceive('foo')->withSomeOfArgs(1, 2);

// 使用闭包自定义匹配逻辑
$mock->shouldReceive('foo')->withArgs(function ($arg) {
    return $arg % 2 == 0; // 只匹配偶数
});

参数捕获

有时需要捕获方法调用时的参数以便后续验证,使用Mockery::capture()

$temp = null;
$mock->shouldReceive('foo')
     ->with(Mockery::capture($temp))
     ->once();

$mock->foo(42);
echo $temp; // 输出42

返回值与异常处理

基本返回值设置

使用andReturn()方法可以为预期的方法调用设置返回值:

$mock->shouldReceive('getUser')
     ->andReturn(['id' => 1, 'name' => 'John']);

序列返回值

如果方法会被多次调用,可通过andReturn()传递多个参数设置序列返回值:

$mock->shouldReceive('count')
     ->andReturn(1, 2, 3);

echo $mock->count(); // 1
echo $mock->count(); // 2
echo $mock->count(); // 3
echo $mock->count(); // 3 (后续调用都返回最后一个值)

也可以使用andReturnValues()传递数组:

$mock->shouldReceive('count')
     ->andReturnValues([1, 2, 3]);

动态返回值

使用andReturnUsing()可以通过闭包动态生成返回值,闭包会接收方法调用时的参数:

$mock->shouldReceive('sum')
     ->andReturnUsing(function ($a, $b) {
         return $a + $b;
     });

echo $mock->sum(2, 3); // 5

返回参数值

使用andReturnArg()可以直接返回指定位置的参数:

$mock->shouldReceive('echo')
     ->andReturnArg(0); // 返回第一个参数

echo $mock->echo('Hello'); // Hello

抛出异常

使用andThrow()方法可以模拟方法抛出异常:

$mock->shouldReceive('fetch')
     ->andThrow(new Exception('Network error'));

// 或者传递异常类名和参数
$mock->shouldReceive('fetch')
     ->andThrow('RuntimeException', 'Invalid parameter', 500);

调用次数验证

验证方法被调用的次数是单元测试中的常见需求,Mockery提供了多种便捷方法:

基本调用次数

$mock->shouldReceive('save')->once(); // 必须调用一次
$mock->shouldReceive('delete')->twice(); // 必须调用两次
$mock->shouldReceive('log')->times(3); // 必须调用三次
$mock->shouldReceive('error')->never(); // 不应该被调用

调用次数范围

// 至少调用2次
$mock->shouldReceive('refresh')->atLeast()->times(2);

// 最多调用5次
$mock->shouldReceive('retry')->atMost()->times(5);

// 调用2-5次之间
$mock->shouldReceive('process')->between(2, 5);

动态调整调用次数

// 允许0次或多次调用
$mock->shouldReceive('notify')->zeroOrMoreTimes();

调用顺序验证

在复杂场景中,可能需要验证方法调用的顺序。使用ordered()方法可以实现这一点:

$mock->shouldReceive('open')->ordered();
$mock->shouldReceive('read')->ordered();
$mock->shouldReceive('close')->ordered();

// 正确的调用顺序
$mock->open();
$mock->read();
$mock->close();

// 如果顺序错误,如先调用read()会抛出异常

分组顺序验证

可以为ordered()指定分组名称,同一组内的方法可以不按顺序调用:

$mock->shouldReceive('connect')->ordered('init');
$mock->shouldReceive('authenticate')->ordered('init');
$mock->shouldReceive('query')->ordered('db');
$mock->shouldReceive('fetch')->ordered('db');

// 'init'组内的方法可以互换顺序
$mock->authenticate();
$mock->connect();

// 'db'组内的方法可以互换顺序
$mock->fetch();
$mock->query();

实战案例:用户服务测试

让我们通过一个实际案例来综合运用Expectations API。假设我们有一个用户服务类,需要测试其transferFunds()方法,该方法涉及多个依赖调用。

// 创建依赖模拟
$userRepo = \Mockery::mock('UserRepository');
$transactionRepo = \Mockery::mock('TransactionRepository');
$notificationService = \Mockery::mock('NotificationService');

// 设置用户仓库预期
$userRepo->shouldReceive('find')
         ->with(1)
         ->once()
         ->andReturn((object)['id' => 1, 'balance' => 1000]);
         
$userRepo->shouldReceive('find')
         ->with(2)
         ->once()
         ->andReturn((object)['id' => 2, 'balance' => 500]);
         
$userRepo->shouldReceive('update')
         ->twice()
         ->with(Mockery::type('object'));

// 设置交易仓库预期
$transactionRepo->shouldReceive('create')
                ->once()
                ->with([
                    'from_user_id' => 1,
                    'to_user_id' => 2,
                    'amount' => 300
                ])
                ->andReturn((object)['id' => 123]);

// 设置通知服务预期
$notificationService->shouldReceive('send')
                    ->twice()
                    ->with(Mockery::type('object'), Mockery::pattern('/funds transferred/'));

// 注入依赖并执行测试
$service = new UserService($userRepo, $transactionRepo, $notificationService);
$result = $service->transferFunds(1, 2, 300);

// 验证结果
assert($result === true);

在这个案例中,我们:

  1. 模拟了三个依赖组件
  2. 为每个依赖设置了详细的调用预期
  3. 验证了方法调用次数、参数和顺序
  4. 测试了整个业务流程

常见问题与最佳实践

避免过度指定

虽然详细的预期可以使测试更精确,但过度指定会导致测试脆弱。只验证那些对测试结果真正重要的交互。

使用默认期望

对于重复出现的期望,可以使用byDefault()设置为默认期望,简化测试代码:

$mock->shouldReceive('log')
     ->withAnyArgs()
     ->once()
     ->byDefault();

清理模拟对象

务必在测试结束时调用Mockery::close(),确保所有期望都得到验证:

public function tearDown(): void
{
    \Mockery::close();
    parent::tearDown();
}

使用部分模拟

当需要测试类的部分方法,同时模拟其他方法时,可以使用部分模拟:

$mock = \Mockery::mock('MyClass[methodToMock]');
$mock->shouldReceive('methodToMock')->andReturn('mocked');
// 其他方法会调用真实实现

总结

Mockery的Expectations API提供了强大而灵活的工具,帮助你构建复杂的测试场景。通过本文介绍的方法,你可以精确控制模拟对象的行为,验证方法调用的各个方面,从而编写出更健壮、更可维护的单元测试。

关键要点:

  • 使用shouldReceive()开始设置期望
  • 利用with()系列方法进行参数验证
  • 使用andReturn()系列方法设置返回值
  • 通过once()/times()等方法验证调用次数
  • 使用ordered()控制调用顺序
  • 合理组织测试代码,避免过度指定

掌握这些技巧后,你将能够应对各种复杂的测试场景,提高单元测试的质量和效率。

如果你想深入了解更多高级用法,可以参考官方文档测试用例

如果你觉得本文对你有帮助,请点赞收藏,并关注我们获取更多关于Mockery和单元测试的优质内容。下期我们将介绍如何使用Mockery模拟静态方法和私有方法,敬请期待!

【免费下载链接】mockery Mockery是一个针对PHP的轻量级模拟对象框架,专为进行依赖注入和隔离测试设计,通过创建模拟对象替代真实依赖,使得单元测试更便捷、高效。 【免费下载链接】mockery 项目地址: https://gitcode.com/gh_mirrors/mo/mockery

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值