在 Lumen 的路由控制器中模拟方法调用以进行测试
Mocking a method call in a route controller in Lumen for testing
我在实现路由控制器的 Lumen 应用程序中有以下控制器 class:
<?php
class MyController {
public function route_method(Request $request) {
// some code
$success = $this->private_method($request->get('get_variable'));
// some code
return \response()->json(['results' => $success]);
}
private function private_method($data) {
// some code, calling 3rd party service
return $some_value;
}
}
以及Lumen应用中的如下对应路由web.php
:
<?php
$app->get('/endpoint', ['uses' => 'MyController@route_method']);
现在我想编写单元测试来确认调用 /endpoint
的返回响应 returns 预期的 JSON 响应包含 key/value 对 'results': true
,但不让 route_method()
通过模拟后者调用 private_method()
,因为 - 如评论中所述 - private_method()
调用第三方服务,我想避免这种情况,所以我我想我需要这样的东西:
<?php
class RouteTest extends TestCase {
public function testRouteReturnsExpectedJsonResponse() {
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => true]);
}
}
但是我如何利用 Mockery
来达到这个目的,或者有其他方法来隔离第 3 方调用吗?
您无法模拟此代码这一事实表明代码设计不当。
我在这里展示的例子只是一个想法,重点是创建一个新的 class 代表与第 3 方系统的通信。
<?php
namespace App\Http\Controllers;
use App\Services\MyService;
class MyController
{
public function __construct(MyService $service)
{
$this->service = $service;
}
public function route_method(Request $request)
{
// some code
$success = $this->service->some_method($request->get('get_variable'));
// some code
return \response()->json(['results' => $success]);
}
}
然后创建另一个 class 来完成它应该做的事情,遵循单一职责原则
<?
namespace App\Services;
class MyService
{
public function some_method($variable)
{
//do something
}
}
那你就可以正常mock了:
<?php
class RouteTest extends TestCase {
public function testRouteReturnsExpectedJsonResponse() {
$service = $this->getMockBuilder('App\Services\MyService')
->disableOriginalConstructor()
->getMock();
$somedata = 'some_data' //the data that mock should return
$service->expects($this->any())
->method('some_method')
->willReturn($somedata);
//mock the service instance
$this->app->instance('App\Services\MyService', $service);
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => true]);
}
}
基本上,你不会。
您应该测试行为,而不是实施。私有方法是一个实现细节。
尽管如此,您仍然可以随心所欲,Laravel/Lumen 中提供了许多选项:
正确的做法:
看看@Felippe Duarte 的回答。要使用 Mockery 而不是 PHPUnit 添加测试代码进行模拟:
<?php
class RouteTest extends TestCase
{
public function testRouteReturnsExpectedJsonResponse()
{
$someData = 'some_data'; //the data that mock should return
$service = Mockery::mock('App\Services\MyService');
$service->shouldReceive('some_method')->once()->andReturn($someData);
//mock the service instance
$this->app->instance('App\Services\MyService', $service);
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => $someData]);
}
}
服务容器的滥用方式:
控制器:
<?php
class MyController {
public function route_method(Request $request) {
// some code
$success = $this->private_method($request->get('get_variable'));
// some code
return \response()->json(['results' => $success]);
}
private function private_method($data) {
// say third party is some paypal class
$thirdParty = app(Paypal::class);
return $thirdParty->makePayment($data);
}
}
测试:
<?php
class RouteTest extends TestCase
{
public function testRouteReturnsExpectedJsonResponse()
{
$someData = 'some_data'; //the data that mock should return
$paypalMock = Mockery::mock(Paypal::class);
$paypalMock->shouldReceive('makePayment')->once()->with('get_value')->andReturn($someData);
//mock the service instance
$this->app->instance(Paypal::class, $paypalMock);
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => $someData]);
}
}
这会起作用,因为 Laravel 的 Service Container 会识别您定义的当尝试实例化 Paypal::class
时,它应该 return 测试中制作的模拟.
不推荐这样做,因为它很容易被滥用,而且不是很明确。
我在实现路由控制器的 Lumen 应用程序中有以下控制器 class:
<?php
class MyController {
public function route_method(Request $request) {
// some code
$success = $this->private_method($request->get('get_variable'));
// some code
return \response()->json(['results' => $success]);
}
private function private_method($data) {
// some code, calling 3rd party service
return $some_value;
}
}
以及Lumen应用中的如下对应路由web.php
:
<?php
$app->get('/endpoint', ['uses' => 'MyController@route_method']);
现在我想编写单元测试来确认调用 /endpoint
的返回响应 returns 预期的 JSON 响应包含 key/value 对 'results': true
,但不让 route_method()
通过模拟后者调用 private_method()
,因为 - 如评论中所述 - private_method()
调用第三方服务,我想避免这种情况,所以我我想我需要这样的东西:
<?php
class RouteTest extends TestCase {
public function testRouteReturnsExpectedJsonResponse() {
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => true]);
}
}
但是我如何利用 Mockery
来达到这个目的,或者有其他方法来隔离第 3 方调用吗?
您无法模拟此代码这一事实表明代码设计不当。 我在这里展示的例子只是一个想法,重点是创建一个新的 class 代表与第 3 方系统的通信。
<?php
namespace App\Http\Controllers;
use App\Services\MyService;
class MyController
{
public function __construct(MyService $service)
{
$this->service = $service;
}
public function route_method(Request $request)
{
// some code
$success = $this->service->some_method($request->get('get_variable'));
// some code
return \response()->json(['results' => $success]);
}
}
然后创建另一个 class 来完成它应该做的事情,遵循单一职责原则
<?
namespace App\Services;
class MyService
{
public function some_method($variable)
{
//do something
}
}
那你就可以正常mock了:
<?php
class RouteTest extends TestCase {
public function testRouteReturnsExpectedJsonResponse() {
$service = $this->getMockBuilder('App\Services\MyService')
->disableOriginalConstructor()
->getMock();
$somedata = 'some_data' //the data that mock should return
$service->expects($this->any())
->method('some_method')
->willReturn($somedata);
//mock the service instance
$this->app->instance('App\Services\MyService', $service);
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => true]);
}
}
基本上,你不会。
您应该测试行为,而不是实施。私有方法是一个实现细节。
尽管如此,您仍然可以随心所欲,Laravel/Lumen 中提供了许多选项:
正确的做法:
看看@Felippe Duarte 的回答。要使用 Mockery 而不是 PHPUnit 添加测试代码进行模拟:
<?php
class RouteTest extends TestCase
{
public function testRouteReturnsExpectedJsonResponse()
{
$someData = 'some_data'; //the data that mock should return
$service = Mockery::mock('App\Services\MyService');
$service->shouldReceive('some_method')->once()->andReturn($someData);
//mock the service instance
$this->app->instance('App\Services\MyService', $service);
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => $someData]);
}
}
服务容器的滥用方式:
控制器:
<?php
class MyController {
public function route_method(Request $request) {
// some code
$success = $this->private_method($request->get('get_variable'));
// some code
return \response()->json(['results' => $success]);
}
private function private_method($data) {
// say third party is some paypal class
$thirdParty = app(Paypal::class);
return $thirdParty->makePayment($data);
}
}
测试:
<?php
class RouteTest extends TestCase
{
public function testRouteReturnsExpectedJsonResponse()
{
$someData = 'some_data'; //the data that mock should return
$paypalMock = Mockery::mock(Paypal::class);
$paypalMock->shouldReceive('makePayment')->once()->with('get_value')->andReturn($someData);
//mock the service instance
$this->app->instance(Paypal::class, $paypalMock);
// need to mock the private_method here somehow first, then...
$this->json('GET', '/endpoint', ['get_variable' => 'get_value'])->seeJson(['results' => $someData]);
}
}
这会起作用,因为 Laravel 的 Service Container 会识别您定义的当尝试实例化 Paypal::class
时,它应该 return 测试中制作的模拟.
不推荐这样做,因为它很容易被滥用,而且不是很明确。