0

I am unit testing my Laravel 4 Controller by mocking my repository that the controller expects. The problem is with the "store" function. This is the function that is called by Laravel when I do a POST to the given controller. The function gets called, but it is expected itemData as an input but I don't know how to provide that. Here is what I've tried:

ItemEntryController

class ItemEntryController extends BaseController
{
    protected $itemRepo;

    public function __construct(ItemEntryRepositoryInterface $itemRepo)
    {
        $this->itemRepo = $itemRepo;
    }

    public function store()
    {
        if(Input::has('itemData'))
        {
            $data = Input::get('itemData');

            return $this->itemRepo->createAndSave($data);
        }
    }
}

Test class

<?php

use \Mockery as m;

class ItemEntryRouteAndControllerTest extends TestCase {

protected $testItemToStore = '{"test":12345}';

public function setUp()
{
    parent::setUp();
    $this->mock = $this->mock('Storage\ItemEntry\ItemEntryRepositoryInterface');
}

public function mock($class)
{
    $mock = m::mock($class);
    $this->app->instance($class, $mock);

    return $mock;
}

public function testItemStore()
{
    Input::replace($input = ['itemData' => $this->testItemToStore]);

    $this->mock
        ->shouldReceive('createAndSave')
        ->once()
        ->with($input);

    $this->call('POST', 'api/v1/tools/itementry/items');
}
Rubens Mariuzzo
  • 28,358
  • 27
  • 121
  • 148
Thelonias
  • 2,918
  • 3
  • 29
  • 63

1 Answers1

2

Well, you got a few options.

Integration testing

You may want to follow the unit testing docs, which actually has a call() method which allows you set all of this. This bootstraps the app and will use your databases, etc.

This is more of an integration test than unit test, as it uses your actual class implementations.

This may actually be preferable, as Unit testing controllers may not actually make much sense (it doesn't do much, in theory, but call other already-unit-tested classes). But this gets into unit testing vs integration testing vs acceptance testing and all the nuances that apply therein. (Read up!)

Unit Testing

If you're actually looking to unit test, then you need to make your controller unit-testable (ha!). This (likely) means injecting all dependencies:

class ItemEntryController extends BaseController
{
    protected $itemRepo;

    // Not pictured here is actually making sure an instance of
    // Request is passed to this controller (via Service Provider or
    // IoC binding)
    public function __construct(ItemEntryRepositoryInterface $itemRepo, Request $input)
    {
        $this->itemRepo = $itemRepo;
        $this->request = $input;
    }

    public function store()
    {
        if($this->input->has('itemData'))
        {
            // Get() is actually a static method so we use
            // the Request's way of getting the $_GET/$_POST variables
            // see note below!
            $data = $this->input->input('itemData');

            return $this->itemRepo->createAndSave($data);
        }
    }
}

Sidenote: The Input facade is actually an instance of Request objet with an extra static method get()!

So now that we aren't using Input any longer, and are injecting the Request object, we can unit test this class by mocking the Request object.

Hope that helps!

Rubens Mariuzzo
  • 28,358
  • 27
  • 121
  • 148
fideloper
  • 12,213
  • 1
  • 41
  • 38
  • AFAIK `call()` allows you to handle passing parameters to functions that expect parameters, but does not do anything with the "Input" facade, which is necessary to 1) get to the URI's query string and 2) pass a JSON payload to the function. That being said, I agree that this would be more integration testing that unit testing since I don't need to call my specific implementation to unit test...I could just mock certain things and test the logic all within a single function. – Thelonias Aug 15 '13 at 12:10