代码之家  ›  专栏  ›  技术社区  ›  MMSs

在Lumen中模拟路由控制器中的方法调用以进行测试

  •  1
  • MMSs  · 技术社区  · 6 年前

    我的Lumen应用程序中有以下实现路由控制器的控制器类:

    <?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;
        }
    }
    

    以及流明应用中的以下相应路径 web.php :

    <?php
    
    $app->get('/endpoint', ['uses' => 'MyController@route_method']);
    

    现在我想编写一个单元测试来确认调用返回的响应 /endpoint 返回预期的JSON响应,该响应包含 'results': true ,但不允许 route_method() 呼叫 private_method() 嘲笑后者,因为正如评论中所说 private\u方法() 打电话给第三方服务,我想避免这种情况,所以我想我需要这样的服务:

    <?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 为此,是否有其他方法隔离第三方呼叫?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Steve Chamaillard    6 年前

    基本上,你没有。

    您应该测试行为,而不是实现。私有方法是一个实现细节。

    尽管如此,您可以随心所欲,Laravel/Lumen提供了许多选项:

    正确的方法:

    看看@Felippe Duarte的答案。要使用mockry而不是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]);
        }
    }
    

    这将起作用,因为 Service Container 在尝试实例化时,Laravel将识别您定义的 Paypal::class ,它应该返回在测试中创建的模拟。

    不建议这样做,因为它很容易被滥用,而且不太明确。

        2
  •  2
  •   Felippe Duarte    6 年前

    您不能模拟此代码的事实是代码设计不好的迹象。 我在这里展示的示例只是一个想法,重点是创建一个新类,表示与第三方系统的通信。

    <?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]);
        }
    }
    

    然后按照单一责任原则创建另一个类来完成它应该做的事情

    <?
    
    namespace App\Services;
    
    class MyService
    {
        public function some_method($variable)
        {
            //do something
        }
    }
    

    然后您可以适当地模拟:

    <?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]);
        }
    }