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

时间:2018-04-05 15:34:52

标签: php unit-testing mocking lumen mockery

我的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;
    }
}

Lumen应用程序web.php中的以下相应路由:

<?php

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

现在我想编写单元测试,确认调用返回的响应/endpoint返回包含'results': true的键/值对的预期JSON响应,但不允许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,还是有另一种隔离第三方通话的方式?

2 个答案:

答案 0 :(得分:1)

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

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

答案 1 :(得分:1)

基本上,你没有。

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

尽管如此,你可以自由地做任何你想做的事情,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时会定义,它应该返回在测试中创建的模拟。

不推荐这样做,因为它很容易被滥用,而且不是很明确。