Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test routes in Laravel 5, or Trying to "MockStub" something, or I have no idea of TDD

I'm starting with TDD and Laravel. Specifically, I'm starting with routes. I defined some and I defined it badly, so excited as I was with the "new" concept of TDD I wanted to write some test for them.

The idea was to test the routes and only the routes, in isolation, as everything I've readed about TDD recomends. I know I can do a $this->call->('METHOD','something') and test response is OK or whatever, but I would like to know that the right method of the right controller is called.

So, I thought that I could mock the controller. This was my first attempt:

public function test_this_route_work_as_expected_mocking_the_controller()
{
    //Create the mock
    $drawController = \Mockery::mock('App\Http\Controllers\DrawController');
    $drawController->shouldReceive('show')->once();

    // Bind instance of my controller to the mock
    App::instance('App\Http\Controllers\DrawController', $drawController);

    $response = $this->call('GET','/draw/1');

    // To see what fails. .env debugging is on
    print($response);
}

The route is Route::resource('draw', 'DrawController');, I know it's ok. But method show is not called. In the response it can be seen: "Method Mockery_0_App_Http_Controllers_DrawController::getAfterFilters() does not exist on this mock object". So I tried to:

$drawController->getAfterFilters()->willReturn(array());

But I get:

BadMethodCallException: Method Mockery_0_App_Http_Controllers_DrawController::getAfterFilters() does not exist on this mock object

After some testing, I was able to arrive to this solution:

public function test_this_route_work_as_expected_mocking_the_controller_workaround()
{
    //Create the mock
    $drawController = \Mockery::mock('App\Http\Controllers\DrawController');
    // These are the methods I would like to 'stub' in this mock
    $drawController->shouldReceive('getAfterFilters')->atMost(1000)->andReturn(array());
    $drawController->shouldReceive('getBeforeFilters')->atMost(1000)->andReturn(array());
    $drawController->shouldReceive('getMiddleware')->atMost(1000)->andReturn(array());
    // This is where the corresponding method is called. I can assume all is OK if we arrive here with
    // the right method name:
    // public function callAction($method, $parameters)
    $drawController->shouldReceive('callAction')->once()->with('show',Mockery::any());

    // Bind instance of my controller to the mock
    App::instance('App\Http\Controllers\DrawController', $drawController);

    //Act
    $response = $this->call('GET','/draw/1');
}

But I would like to change the shouldReceives for willReturns: the atMost(1000) are hurting my eyes. So the questions I have are:

1) Is there a cleaner way to test ONLY the routes in Laravel 5? I mean, the ideal scenario will be one in which the controller doesn't exist but, if the route is ok, the test pases

2) Is it possible to "MockStub" the controllers? What's the better way to do it?

Thank you very much.

like image 653
Alvaro Maceda Avatar asked Dec 20 '22 05:12

Alvaro Maceda


1 Answers

I've finally got it. You need a partial mock. It can be done as simple as this (the trick is including an "array" of methods to mock to Mockery::mock):

public function test_this_route_work_as_expected_mocking_partially_the_controller()
{
    //Create the mock
    $drawController = \Mockery::mock('App\Http\Controllers\DrawController[show]');
    $drawController->shouldReceive('show')->once();

    // Bind instance of my controller to the mock
    App::instance('App\Http\Controllers\DrawController', $drawController);

    //Act
    $this->call('GET','/draw/1');
}

And, if you create a partial mock of all controllers in setup() method, all route tests can be grouped in a single (or a couple) of TestCases

like image 58
Alvaro Maceda Avatar answered May 04 '23 20:05

Alvaro Maceda