测试过程中,mock一个类的静态方法,如何释放这个mock?
在测试第一个方法中,mock一个对象后,在第二个方法中怎么释放这个mock对象?可能我说的有点抽象,我先列一下我需要用到的类文件,后面我会详细说明过程,以及遇到的问题。
接口类:
namespace Contracts;
interface Pay
{
public static function getMoney();
}
ServiceProvider:
class AppServiceProvider extends \Illuminate\Support\ServiceProvider
{
public function register()
{
// 这里绑定了支付渠道接口(Pay)的实现为微信支付(Wechat)
$this->app->bind(\Contracts\Pay::class, \Instances\Wechat::class);
}
}
被测试类:
namespace Controllers;
// 钱包相关的控制器
class WalletController
{
public $pay;
public function __construct(\Contracts\Pay $pay)
{
$this->pay = $pay;
}
// 获取当前支付渠道的余额
public function getBalance()
{
return $this->pay->getMoney();
}
}
测试过程:
class baseTest extends \Illuminate\Foundation\Testing\TestCase
{
// 测试程序会按照顺序执行
// 这个会是第一个执行的方法
public function testOne()
{
// 第一步:
// 伪造类中的静态方法需要这么写'alias:{namespace\className}'
$mock_pay = \Mockery::mock('alias:\Contracts\Pay');
// 这里对mock出的实例进行配置,在调用 getMoney() 方法后返回 60
$mock_pay->shouldReceive('getMoney')->andReturn(60);
// 第二步:
// 为了测试中不触发真的金钱变动,这里绑定mock的实例
$this->app->bind(\Contracts\Pay::class, function($app) use($mock_pay) {
return $mock_pay;
});
// 第三步:
// 接下来测试被测试的代码块
$ctrl = $this->app->make(\Controllers\WalletController::class);
/**
* 多谢 @leo 和 @zhangrongwang 的指正,此处已做修改
* 原【$ctrl = new \Controllers\WalletController; 】这种写法是错误的
* 只有当 WalletController 中,构造函数的参数的个数为 0 时,才可以不加小括号
* 本例中 WalletController 的构造函数有一个参数,则需要通过 Container 来帮忙
* 创建一个包含 $pay 参数的 $ctrl。
*/
// 调用被测试的方法
$this->assertEquals(60, $ctrl->getBalance());// 成功
}
// 继续执行第二个测试
// 这个是关键,这里会出问题
public function testTwo()
{
// 我直接开始测试代码块
// 这里会直接报错
$ctrl = $this->app->make(\Controllers\WalletController::class);
// 调用被测试的方法
$this->assertEquals(60, $ctrl->getBalance());
}
}
上面是我的测试过程,
- 如果单独执行
testOne()
是可以通过的。 - 单独执行
testTwo()
也是可以通过的。 - 一旦两个都开启测试,那么
testTwo()
会直接报错:PHP Fatal error: Instances\Wechat cannot implement Contracts\Pay - it is not an interface in ...
原因是testOne()
中已经把\Contracts\Pay
这个接口变为了mockery
实例,不再是一个接口了。
- 我尝试过在
testTwo()
中执行$this->refreshApplication();
public function testTwo() { $this->refreshApplication(); . . }
- 也试过重新绑定
$this->app->rebinding();
public function testTwo() { $this->app->rebinding(\Contracts\Pay::class, \Instances\Wechat::class); . . }
但都没有用,所以我想问下:怎么才能在testTwo()
中,恢复AppServiceProvider
中的绑定关系?
运行环境
root@Aliyun-ECS / # php -v
PHP 7.1.33 (cli) (built: Oct 26 2019 10:16:23) ( NTS )
root@Aliyun-ECS / # php artisan --version
Laravel Framework version 5.3.22
root@Aliyun-ECS / # vendor/phpunit/phpunit/phpunit --version
PHPUnit 5.6.2 by Sebastian Bergmann and contributors.
感谢 @zhangrongwang 哥哥的帮助,现在我们可以进行静态方法的模拟了,而且模拟的操作不会影响到后面的测试。
phpunit
不支持模拟静态方法:所以我们需要通过
Composer
引入Mockery
扩展包:现在我们就可以生成
\Contracts\Pay
接口的 桩件,替换掉原接口的绑定,而且不会影响后面的测试。接下来我们修改下单元测试:此问题已解决,再次感谢 @zhangrongwang 的帮助。