What's the best way to unit test a controller in Laravel without testing the route too
Asked Answered
D

3

15

I've read lots of documentation about testing controllers using $this->call($destination, $parameters, 'GET'); but this seems to rely on the route being set up too, and knowing the right $destination to use.

Generally this is OK, but accessing a controller from a route doesn't seem right for unit testing. I want to unit test the controller, not the route. Is there a standard way to unit test controllers, without dealing with routes?

Is simply manually instantiating the controller and calling the method enough? E.g.

$controller = new MyController;
$response = $controller->someMethod($param);
$this->assertSomething($response);

Perhaps controllers shouldn't be unit tested (and only have acceptance tests) and my request is a sign that my controllers are too heavy.

Delve answered 26/9, 2013 at 13:33 Comment(3)
Probably, Possible, Maybe. Not that I know Laravel well, but the first question you could try your own - the just instantiating the controller one. If there is some Service Container involved with Laravel and if your controller makes use of it you might have some collaborators you need to mock then, but perhaps you can encapsulate such in a TestCase of it's own and extend from it to keep unit-testing controllers more handsome. But writing tests is like writing code, you need to try as well if that works.Hesperidin
Instantiating the controller seems to be working OK so far but I'm concerned it's not the "Laravel way". I'm extending my test case from Laravel's, which automatically sets up the "app" DIC.Delve
Well if it sets that DIC app with testing doubles, this is probably OK for testing. If not then things might become fishy. You probably should discuss this a little in the Laravel IRC chat, I can imagine this kind of feedback is wanted there. The folks there should be able to tell you also more details about Laravel then I can do.Hesperidin
B
13

You can call your actions directly:

$response = $this->action('GET', 'OrdersController@show', ['id' => 1]);
Blowbyblow answered 27/9, 2013 at 13:57 Comment(6)
This is a good step. Do I have to know the correct HTTP method (eg GET)?Delve
Yes, testcase's action method needs the HTTP method and you should be thorough in your tests, so it's also a good practice.Blowbyblow
My point is that the controller (eg OrdersController::show()) shouldn't be coupled to the route's HTTP method, and shouldn't have to know about it.Delve
@AntonioCarlosRibeiro ,it's won't work for me? what i have to do?Vinculum
@AntonioCarlosRibeiro I have a question: I'm working in an environment where Controller names can change at times due to different builds but route destinations never change. So imagine a huge application with hundreds of routes and different controllers and a test for each controller. I want to know if there's a way to do a $this->call('POST', users, ['id' =>1]); users being the specified route from Route::resource('users', UsersRestfulController', array('only' => array('index', 'show', 'store', 'update'))); I want this so that when controllers change, test maintainabilityIsANoBrainerChartography
$this->action was removed since 5.4Melodic
C
2

In laravel 6 any controller method can be called directly as follows:

app()->call('App\Http\Controllers\TestController@testMethod', [$param1, $param2]);

that's it.

Consalve answered 12/6, 2020 at 13:35 Comment(0)
E
0

The only thing I could get working in Laravel 9 (including with parameters like $request) was:

app()->make("App\Http\Controllers\MyController")->callAction("myAction", [$manufacturer, $request])
Elmer answered 23/2 at 1:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.