测试时:
我想查看我网站上的商品,模拟确认,然后继续处理订单的其余部分。
我如何换出模拟的好代码?如:
$gateway = $checkout->getGateway($params['payment_method']);
$response = $gateway->completePurchase($params)->send();
这很容易吗?
基本方向非常感谢,虽然我已经创建了测试,但我在模拟领域的知识是基本的
答案 0 :(得分:3)
就模拟而言,您不需要知道确切的响应,您只需要知道输入和输出数据,就应该在laravel服务提供者中替换服务(在这种情况下为Paypal)。您需要像下面这样一些步骤:
首先向您的laravel服务提供商添加PaymentProvider
:
class AppServiceProvider extends ServiceProvider
{
...
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(PaymentProviderInterface::class, function ($app) {
$httpClient = $this->app()->make(Guzzle::class);
return new PaypalPackageYourAreUsing($requiredDataForYourPackage, $httpClient);
});
}
...
}
然后在测试类中,应使用该接口的模拟版本替换提供程序:
class PaypalPackageTest extends TestCase
{
/** @test */
public function it_should_call_to_paypal_endpoint()
{
$requiredData = $this->faker->url;
$httpClient = $this->createMock(Guzzle::class);
$paypalClient = $this->getMockBuilder(PaymentProviderInterface::class)
->setConstructorArgs([$requiredData, $httpClient])
->setMethod(['call'])
->getMock();
$this->instance(PaymentProviderInterface::class, $paypalClient);
$paypalClient->expects($this->once())->method('call')->with($requiredData)
->willReturn($httpClient);
$this->assertInstanceOf($httpClient, $paypalClient->pay());
}
}
答案 1 :(得分:0)
当我必须模拟包含对外部库的调用的方法(例如您的情况下的Omnipay
)时,通常使用这种方法。
您的代码段不是很广泛,但是我假设您的课程看起来像这样:
class PaymentProvider
{
public function pay($request)
{
$gateway = Omnipay::create('paypal');
$response = $gateway->purchase($request['params'])->send();
if ($response->isSuccessful()) {
// do more stuff
}
}
}
我要做的是重构类,以便对外部库的调用位于单独的方法内:
class PaymentProvider
{
protected function purchaseThroughOmnipay($params)
{
$gateway = Omnipay::create('paypal');
return $gateway->purchase($params)->send();
}
public function pay($request)
{
$response = $this->purchaseThroughOmnipay($request['params']);
if ($response->isSuccessful()) {
// do more stuff
}
}
}
然后,在此重构之后,在测试类中,我们可以利用PHPunit的getMockBuilder
给我们带来的多种可能性:
<?php
use PHPUnit\Framework\TestCase;
class PaymentProviderTest extends TestCase
{
protected $paymentProvider;
protected function setUp()
{
$this->paymentProvider = $this->getMockBuilder(\PaymentProvider::class)
->setMethods(['pay'])
->getMock();
}
public function testPay()
{
// here we set up all the conditions for our test
$omnipayResponse = $this->getMockBuilder(<fully qualified name of the Omnipay response class>::class)
->getMock();
$omnipayResponse->expects($this->once())
->method('isSuccessful')
->willReturn(true);
$this->paymentProvider->expects($this->once())
->method('purchaseThroughOmnipay')
->willReturn($omnipayResponse);
$request = [
// add relevant data here
];
// call to execute the method you want to actually test
$result = $this->paymentProvider->pay($request);
// do assertions here on $result
}
}
对正在发生的事情的一些解释:
$this->paymentProvider = $this->getMockBuilder(\PaymentProvider::class)
->setMethods(['pay'])
->getMock();
这为我们提供了Payment
类的模拟实例,对于该实例,pay
是一个“真实”方法,其实际代码将被实际执行,而所有其他方法(在我们的情况下为{{1 }}是我们关心的对象)是可以覆盖其返回值的存根。
以同样的方式,这里我们在模拟响应类,以便我们可以控制其行为并影响purchaseThroughOmnipay
方法的流程:
pay
这里的区别是我们没有调用$omnipayResponse = $this->getMockBuilder(<fully qualified name of the Omnipay response class>::class)
->getMock();
$omnipayResponse->expects($this->once())
->method('isSuccessful')
->willReturn(true);
,这意味着 all 此类的方法将是存根,我们可以为其覆盖返回值(这正是我们正在为setMethods
做)。
当然,如果在isSuccessful
方法中调用了更多此类的方法(大概在pay
之后),那么您可能不得不多次使用if
。