Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

alexhiggins's avatar

Testing Controller

Hey,

I'm trying to unit test a controller. I know most of you (including me) would just use a functional / acceptance test for this. But, I want to give this a go just so I know how to do it. Anyways, mockery is new to me also so I'm guessing a controller would not be a great starting point but shrugs

Here's my controller

<?php

use CookBook\Recipes\RecipeRepository;

class HomeController extends BaseController {

 /**
  * @var RecipeRepository
  */
 protected $recipe;

 /**
  * @param RecipeRepository $recipe
  */
 public function __construct(RecipeRepository $recipe)
 {
    $this->recipe = $recipe;
 }

 /**
  * @return \Illuminate\View\View
  */
 public function index()
 {
    $recipes = $this->recipe->getAllPaginated();

    return View::make('home.index', compact('recipes'));
 }
}

So to test this I need to assert get all paginated has been called once and the view has the recipes variable available right?

Here's my test

<?php

class HomeControllerTest extends TestCase {

 public function tearDown()
 {
    Mockery::close();
 }

 /**
  * @test
  * @return void
  */
 public function index()
 {
    $mock = Mockery::mock('CookBook\Recipes\RecipeRepository');
    $this->app->instance('CookBook\Recipes\RecipeRepository', $mock);
    $mock->shouldReceive('getAllPaginated')->once();

    $this->call('GET', '/');

    $this->assertResponseOk();
    $this->assertViewHas('recipes');
 }

}

(just for reference)

 public function getAllPaginated($howMany = 12)
 {
    return $this->model->with('user', 'tags')->latest()->paginate($howMany);
 }

The error i get is this

FATAL ERROR. TESTS NOT FINISHED. Call to a member function chunk() on null

That's perfectly understandable, you can't call chunk on null as my mock does not return anything.

What do I need to set ->andReturn to? Or what would you do differently to make this a legit test?

Cheers

0 likes
7 replies
pmall's avatar

Yes you should use andReturn so your mock returns a dummy collection of models.

alexhiggins's avatar

That's where I'm a bit stuck to be honest. It's not just 'foo' I'm returning it's the results of that getAllPaginated method.

pmall's avatar

In fact what you return doesnt matter. The only point is to ensure that what is returned by your repository method is assigned to the correct view variable. (And as a repo returns a collection your mocked repo should return a dummy recipe collection. The view will use chunk on it.).

Let's say your controller action use an injected service, and you execute a method of this service with the data returned by the repository as parameter. With your controller test you just want to ensure this flow is respected, whatever the data is. You would do this :

$data = new Collection([$dummy_recipe_instance, ...]);
$repo_mock = //mocking your repo
$service_mock = //mocking your service

$repo_mock->shouldReceive('getAllRecipes')->andReturn($data);
$service_mock->shouldReceive('foo')->with($data);

// request

Tada ! You are sure this flow will be respected, regardless of the data and what getAllRecipes or foo does ! (They will have their own tests) This is the ultimate goal of a controller to pass data through different services and that what you want to test.

Jeffrey's test dummy package is very usefull to generate random collection of models.

1 like
alexhiggins's avatar

Nice, that works unless you have pagination. I also wouldn't want to use test dummy for these as I don't want to be working with the database, an empty collection would do just fine.

 public function index()
 {
  $mock = Mockery::mock('CookBook\Recipes\RecipeRepository');
  $this->app->instance('CookBook\Recipes\RecipeRepository', $mock);

  $mock->shouldReceive('getAllPaginated')
   ->once()
   ->andReturn(Paginator::make([], 0, 5));

  $this->call('GET', '/');

  $this->assertResponseOk();
  $this->assertViewHas('recipes');
 }

Look about right?

pmall's avatar

@alexhiggins Yes if your repo method returns a paginator your mocked repo should return a dummy paginator.

I suggest to populate it with dummy instances. I know it become a bit philosophical but if you return an empty paginator object, you ensure that your view receive an empty paginator object, not especially the one that came out of your repo.

alexhiggins's avatar

I was under the impression you wouldn't care what is returned from 'getAllPaginated' because it will have its own tests to verify it's working. All you would care about is that it has been called. (hence it been mocked).

In short though, the test's now green and this makes a bit more sense to me :) thank you for your help.

pmall's avatar

You are not testing if getAllPaginated works well, you are testing that its return value is correctly transmitted to other services. It this particular case, to a view variable :)

Please or to participate in this conversation.