First of all, I'm aware the docs states:
Note: You should not mock the Request facade. Instead, pass the input you desire into the HTTP helper methods such as call and post when running your test.
But those kind of tests are more like integration or functional since even though you're testing a controller (the SUT
), you're not decoupling it from it's dependencies (Request
and others, more on this later).
So what I'm doing, in order to do the correct TDD
loop, is mocking the Repository
, Response
and Request
(which I have problems with).
My test look like this:
public function test__it_shows_a_list_of_categories() {
$categories = [];
$this->repositoryMock->shouldReceive('getAll')
->withNoArgs()
->once()
->andReturn($categories);
Response::shouldReceive('view')
->once()
->with('categories.admin.index')
->andReturnSelf();
Response::shouldReceive('with')
->once()
->with('categories', $categories)
->andReturnSelf();
$this->sut->index();
// Assertions as mock expectations
}
This works perfectly fine, and they follow the Arrange, Act, Assert style.
The problem is with Request
, like the following:
public function test__it_stores_a_category() {
Redirect::shouldReceive('route')
->once()
->with('categories.admin.index')
->andReturnSelf();
Request::shouldReceive('only')
->once()
->with('name')
->andReturn(['name' => 'foo']);
$this->repositoryMock->shouldReceive('create')
->once()
->with(['name' => 'foo']);
// Laravel facades wont expose Mockery#getMock() so this is a hackz
// in order to pass mocked dependency to the controller's method
$this->sut->store(Request::getFacadeRoot());
// Assertions as mock expectations
}
As you can see I have mocked Request::only('name')
call. But when I run $ phpunit
I get the following error:
BadMethodCallException: Method Mockery_3_Illuminate_Http_Request::setUserResolver() does not exist on this mock object
Since I'm not calling directly setUserResolver()
from my controller, that means it is called directly by the implementation of Request
. But why? I mocked the method call, it shouldn't be calling any dependency.
What am I doing wrong here, why am I getting this error message?
PS: As a bonus, am I looking it the wrong way by forcing TDD with Unit Tests on Laravel framework, as it seems the documentation is oriented to integration testing by coupling interaction between dependencies and SUT with $this->call()
?
I tried mocking the Request for my tests too with no success. Here is how I test if item is saved:
public function test__it_stores_a_category() {
$this->action(
'POST',
'CategoryController@store',
[],
[
'name' => 'foo',
]
);
$this->assertRedirectedTo('categories/admin/index');
$this->seeInDatabase('categories', ['name' => 'foo']);
}
Hope it is of some help
Unit testing a controller when using Laravel doesn't seem like a great idea. I wouldn't be concerned with the individual methods that are being called on the Request, Response and maybe even the repository classes, given the context of a Controller.
Furthermore, unit testing a controller belonging to a framework because you want to decouple the test sut from its dependencies doesn't make sense, as you can only use that controller in that framework with those given dependencies.
As the request, response and other classes are all fully tested (either via the underlying Symfony class, or by Laravel itself), as a developer I'd concern myself only with testing the code I own.
I'd write an acceptance test.
<?php
use App\User;
use App\Page;
use App\Template;
use App\PageType;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class CategoryControllerTest extends TestCase
{
use DatabaseTransactions;
/** @test */
public function test__it_shows_a_paginated_list_of_categories()
{
// Arrange
$categories = factory(Category::class, 30)->create();
// Act
$this->visit('/categories')
// Assert
->see('Total categories: 30')
// Additional assertions to verify the right categories can be seen may be a useful additional test
->seePageIs('/categories')
->click('Next')
->seePageIs('/categories?page=2')
->click('Previous')
->seePageIs('/categories?page=1');
}
}
Because this test uses the DatabaseTransactions
trait, it's easy to perform the arrange part of the process, which almost allows you to read this as a pseudo-unit test (but that's a slight stretch of the imagination).
Most importantly, this test verifies that my expectations are met. My test is called test_it_shows_a_paginated_list_of_categories
and my version of the test does exactly that. I feel the unit test route only asserts that a bunch of methods are called, but doesn't ever verify that I'm showing a list of the given categories on a page.
You will always run into problems trying to properly unit test controllers. I recommend acceptance testing them with something like Codeception. Using acceptance tests you can ensure your controllers/views are handling any data appropriately.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With