Mocking 2 data repositories at the same time with Laravel 4 - unit-testing

I am developing an application using Laravel 4 and trying to follow TDD. I use, following tutorials from Jeffrey Way or Philip Brown , repositories for my database. I had problems with that before ( Mockery not calling method from repository (interface) ) but everything is working fine now in my tests. However, I do get an error trying to mock 2 repositories in the same test, like this:
class PedidosControllerTest extends TestCase {
private $mock;
private $pedidoModelMock;
private $mockCliente;
private $clienteModelMock;
function setUp() {
parent::setUp();
$this->mock = $this->mock('repositories\canarias\PedidoDbRepository');
$this->pedidoModelMock = Mockery::mock('Pedido');
$this->mockCliente = $this->mock('repositories\canarias\ClienteDbRepository');
$this->clienteModelMock = Mockery::mock('Cliente');
}
public function mock($class)
{
$mock = Mockery::mock('Model', $class);
$this->app->instance($class, $mock);
return $mock;
}
protected function tearDown()
{
Mockery::close();
}
public function testIndexWithClient()
{
$nestedView = 'pedidos.index';
$this->registerNestedView($nestedView);
$this->mockCliente
->shouldReceive('find')
->once()
->with(698)
->andReturn($this->clienteModelMock);
$this->mock
->shouldReceive('findAllFromCliente')
->once()
->with(698)
->andReturn($this->pedidoModelMock);
$this->clienteModelMock
->shouldReceive('getAttribute')
->once()
->with('nombre')
->andReturn('Pepito');
$this->call('GET', '/clientes/698/pedidos');
$this->assertResponseOk();
$this->assertViewHas('pageAttributes');
$this->assertViewHas('contenido');
$this->assertNestedViewHas($nestedView, 'pedidos');
$this->assertNestedViewHas($nestedView, 'cliente');
}
}
From what I've tested (no pun intended), the problem seems to be related with this code shared by both $this->mock and $this->mockCliente:
Mockery::mock('Model', $class);
I get an error saying that Model class doesn't exist. In other functions of the test, where I just use ONE mock, that class is found indeed, so it's not related with the name being misspelled or something like that.
Is that Model class somehow "lost" the first time is mocked?

It turned out it was indeed a problem with the class being mispelled (idiot mistake, I know). It should be
$this->mockCliente = $this->mock('repositories\canarias\ClientesDbRepository');
instead of
$this->mockCliente = $this->mock('repositories\canarias\ClienteDbRepository');

Related

Laravel mockery

I am trying to set up the simplest of tests in my controller but, as with most things Laravel, there are no decent tutorials to demonstrate the simple stuff.
I can run a simple test (in a file called UserControllerTest) like this:
public function testIndex()
{
$this->call('GET', 'users');
$this->assertViewHas('users');
}
This calls the /users route and passes in an array users.
I want to do the same with Mockery but how?
If I try this:
public function testIndex()
{
$this->mock->shouldReceive('users')->once();
$this->call('GET', 'users');
}
I get an error that "Static method Mockery_0_users::all does not exist on this mock object.
Why not? I am mocking User which extends Ardent and in turn extends Eloquent. Why does ::all not exist for the mock?
BTW, these are the set-up functions for Mockery:
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('User');
}
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
You can't directly mock an Eloquent class. Eloquent is not a Facade and your User model neither. There is a bit of magic in Laravel but you can't do things like that.
If you want to mock your User class, you have to inject it in the controller constructor. The repository pattern is a good approach if you want to do that. There is a lot of articles about this pattern and Laravel on Google.
Here some pieces of code to show you how it could look like :
class UserController extends BaseController {
public function __construct(UserRepositoryInterface $users)
{
$this->users = $users;
}
public function index()
{
$users = $this->users->all();
return View::make('user.index', compact('users'));
}
}
class UserControllerTest extends TestCase
{
public function testIndex()
{
$repository = m::mock('UserRepositoryInterface');
$repository->shouldReceive('all')->andReturn(new Collection(array(new User, new User)));
App::instance('UserRepositoryInterface', $repository);
$this->call('GET', 'users');
}
}
If it seems to be too much structuration for your project you can just call a real database in your tests and don't mock your model classes... In a classic project, it just works fine.
This function is part of a project called apiato.io you can use it to mock any class in Laravel, even facade, basically anything that can be resolved with the IoC, which is almost all classes if you are using proper dependency injection:
/**
* Mocking helper
*
* #param $class
*
* #return \Mockery\MockInterface
*/
public function mock($class)
{
$mock = Mockery::mock($class);
App::instance($class, $mock);
return $mock;
}

How to mock App::make() on UnitTesting Laravel 4

i got a question when i was unit testing my application. I Have a method that require a dependency but only that method need it so i thought to don't inject it by construct but initialize it with App::make() of the IoC container Class. But now how can i unit test that?
Let's say a short example for understand how you unit testing this function of example
class Example {
public function methodToTest()
{
$dependency = App::make('Dependency');
return $dependency->method('toTest');
}
}
Test
public function test_MethodToTest() {
$dependency = m::mock('Dependency');
$dependency->shouldReceive('method')->once()->with('toTest')->andReturn(true);
$class = new Example();
$this->assertTrue($class->methodToTest('toTest')); // does not work
}
You're almost there. Create an anonymous mock with the expectations that you need and then register that mock as the instance for Dependency and you should be good to go.
That would look something like this
public function test_MethodToTest() {
$dependency = m::mock();
$dependency->shouldReceive('method')->once()->with('toTest')->andReturn(true);
App::instance('Dependancy', $dependancy);
$class = new Example();
$this->assertTrue($class->methodToTest()); // should work
}
I would prefer to inject the dependency in Example classes constructor.
class Example{
/** #var Dependency */
private $dependency;
public function __construct(Dependency $dependency){
$this->dependency = $dependency;
}
public function methodToTest(){
return $this->dependency->method('toTest');
}
}
class Test{
public function test_MethodToTest(){
$mock = Mockery::mock(Dependency::class);
$mock->shouldReceive('method')->once()->with('toTest')->andReturn(true);
$class = new Example($mock);
$this->assertTrue($class->methodToTest());
}
}
In your controller, libraries you can then use IoC like this
$example = App::make(Example::class);

Laravel - Testing Repositories

I need to test a repository, which has a Eloquent model injected via constructor.
class EloquentOrderRepository implements OrderRepositoryInterface
{
protected $model;
public function __construct(Order $model)
{
$this->model = $model;
}
public function calculateValues(array $deliveryOption = null)
{
if (! is_null($deliveryOption)) {
$this->model->value_delivery = (float) number_format($deliveryOption['price'], 2);
}
$this->model->value_products = (float) number_format($this->model->products->getTotal(), 2);
$this->model->value_total = (float) $this->model->value_products + $this->model->value_delivery;
}
}
My problem is when I call $this->model->value_products (or any of the attributes). The Eloquent model try to call the setAttribute method, which doesn't exist on the mocked model. If I mock this method, I can't set the attribute correctly, and my test assertions will fail.
Here is my test:
<?php
class EloquentOrderRepositoryTest extends \PHPUnit_Framework_TestCase
{
protected $model, $repository;
public function setUp()
{
$this->model = Mockery::mock('Order');
}
public function test_calculate_values()
{
$repository = new EloquentOrderRepository($this->model);
$this->model->products = m::mock('SomeCollection');
$this->model->products->shouldReceive('getTotal')->once()->withNoArgs()->andReturn(25);
$this->model->calculateValues(array('price' => 12));
$this->assertEquals(12, $this->model->value_delivery);
$this->assertEquals(25, $this->model->value_products);
$this->assertEquals(37, $this->model->value_total);
}
}
Any thoughts on this?
I think your main issue is that you're not using the repository pattern correctly. You should think about the passed model in your constructor as a prototype. It's not a real thing to be worked with, but an instance of something you use for other things. In the repository, you may have a method getUnpaidOrders which will do something like return $this->model->wherePaid('0')->get();. As you can see, we're not interacting with the instance as an actual concrete instance but more of something to achieve a broader scope.
In your calculate method you're actually setting values on this prototype model. I don't know what you then intend to do with these but as far as I'm aware this is not what the repository patter is supposed to do. The methods on a repository are generally static-like methods, where you call them (maybe with some input) and get something back. They shouldn't have an effect on any kind of internal state as a repository shouldn't have any kind of internal state.
Hopefully this makes sense.

How to pass JSON to route from unit test?

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');
}
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!

Mockery\Exception\NoMatchingExpectationException PHPUnit test fails

Im testing with PHPUnit and my test fails on a function. But i don't know why.
The function i want to mock:
public function subscribe($email)
{
$message = new SubscribeMessage();
$message->setEmailaddress($email);
$message->setLocale(Locale::getDefault());
$this->getAmqpProducer()->publish($message, 'newsletter-subscribe');
return true;
}
and my Unit test:
public function testSubscribeSendsAmqpMessage()
{
$email = 'email#email.nl';
$locale = 'nl';
$this->amqpProducerMock
->shouldReceive('publish')
->once()
->with(
\Mockery::on(
function ($message, $routingkey) use (&$publishedMessage) {
$publishedMessage = $message;
return $routingkey == 'newsletter-subscribe';
}
)
);
$this->service->subscribe($email, $locale);
}
but the test says:
Mockery\Exception\NoMatchingExpectationException : No matching handler found for AcsiRabbitMq\Producer\Producer::publish(AcsiNewsletter\RabbitMq\Message\SubscribeMessage, "newsletter-subscribe"). Either the method was unexpected or its arguments matched no expected argument list for this method
How can i fix my Unit test? Or how can i refactor my test?
You Mock the subscribe, not the internal publish. When you run the test and call ->subscribe, it will attempt to execute the code in the class. Therefore, it will try to run the subscribe() method, which you appear to have a strange reference to your Mock.
Normally, your test will mock the subscribe, so you can return a value for the assert test, which is hard coded.
You appear to have tried to mock the GetAmqpProducer() object that is in your regular code. You need to either be able to pass the mock object to be used into your class, or to be able to assign it.
Simplified Example:
class Email
{
private $MsgObject;
// Constructor Injection
public __construct(SubscribeMessage $MessageObject)
{
$this->MsgObject = $MessageObject;
...
}
// Setter Injection
public function SetSubscribeMessage(Subscribe $MessageObject)
{
$this->MsgObject = $MessageObject;
}
public function setEmailaddress($email)
{
$this->MsgObject->emailAddress = $email;
...
}
public function setLocale($Locale)
{
$this->MsgObject->Locale = $Locale;
...
}
...
}
Your class sample above has too many internal objects and dependencies to be tested as such, since the test will actually call these. You would use Dependency Injection to pass the objects with known state, and have them return properly.
Please note, I am not showing how to do this in Mockery, as I do not use it, but this simple example should help you understand what I am trying to express.
So a simple test might look like:
public function testSubscribeMessage()
{
$email = 'email#email.nl';
$this->Mock(
->shouldReceive('setEmailAddress')
->once()
->will_return($email)
);
$SubscribeMessage = new SubscribeMessage($this->Mock);
$SetEmail = $SubscribeMessage->setEmailAddress($email);
$this->assertEquals($email, $SetEmail);
}