Laravel 5 and Mockery, doesn't work - php

I've been debugging line per line and it seems the error comes when I try to bind a repository to a mock:
$mock = Mockery::mock('MyNamespace\Repositories\MyModelInterfaceRepository');
// Error is in this line
$this->app->instance('MyNamespace\Repositories\MyModelInterfaceRepository', $mock);
I already bound repo interface and implementation and it works on the browser, it only fails in the test case, giving me an error 500.
My controller's constructor goes like this:
use MyNamespace\Repositories\MyModelInterfaceRepository;
class MyController extends Controller {
public function __construct(MyModelInterfaceRepository $repo) {
$this->repo = $repo;
}
....
Any ideas?
Edit:
Here is the log. It seems to be something about the View not receiving a proper foreach argument, which is possibly caused because the mock call is returning null.
For anyone who also encounters this problem, in this case, should the controller validate if the returned value is null (considering Eloquent would return an empty array if no records, but never null), or should the Mockery make sure it returns a value?

Related

PHPunit: How to change Mock object propriety from an external function

I am using Symfony and I'm trying to test the addStudentCard function in "Student" class, which adds a "StudentCard" object to $studentCards array collection propriety AND a "Student" object to $student propriety in "StudentCard" class. This is how I did it:
class StudentCard {
private $student;
public function getStudent();
public function setStudent();
//...
}
class Student {
private $studentCards;
public function getStudentCards();
public function addStudentCard(StudentCard $studentCard){
$studentCard->setStudent($this);
$this->studentCards[] = $studentCard;
return $this;
//...
}
What I want to achieve is to test this addStudentCard function using a MockBuilder, I have already done this without using mocks by doing:
class StudentTest extends AbstractTestCase {
public function testAddStudentCard(){
$studentCard = new StudentCard();
$student = new Student();
$student->addStudentCard($studentCard);
$student->assertSame($studentCard, $student->getStudentCards()[0]);
$student->assertSame($student, $studentCard->getStudent());
}
This works as expected with no problem.
What I would like is to replace the line:
$studentCard = new StudentCard();
with something like this:
$studentCard = $this->getMockBuilder(StudentCard::class)->getMock();
But what I get is the error:
Failed asserting that null is identical to an object of class Student.
The problem with your scenario is, that you are asserting that the mock returns the original student:
$student->assertSame($student, $studentCard->getStudent());
If the $studentCard is a Mock object, it doesn't return the original object unless you tell it to do so. But since you are already using a mock, there is no need to test that.
What you actually want to test in this case is, that the $student was assigned back to the $studentCard. That is what expectations are for.
So in your particular case you would go with:
$studentCard->expects($this->once())->method('setStudent')->with($student);
// ...
$student->addStudentCard($studentCard);
Make sure that you have the line there (as I shown in the code) before you call addStudentCard, otherwise the test will fail that the expectation has not been met.
After you set the expectations, there is no need to run any assertions (and you should not) on the mock objects.
The answer Ondrej Führer provided is the right answer to the problem I described.
I had also a removeStudentCard method that deletes the student from the studentCard object, so $this->once() was not appropriate for my case. In order to test this, I did exactly the same thing Ondrej Führer suggested with some modifications, so the code line I added was:
$studentCard->expects($this->exactly(2))->method('setStudent')->withConsecutive(
[$student],
[null]
);
//...
$student->addStudentCard($studentCard);
//...
$student->removeStudentCard($studentCard);
This is self explaining, the method setContact is expected to be called exaclty two times with $student as an argument for the first time, and null in the second call.
Hopefully this would be helpful for anyone looking to do something similar.

Laravel View::share() does not work when testing

In my Laravel 4 project I've bound the current user to the views using the share() method like so:
View::share(['currentUser' => Sentry::getUser()]);
This works when browsing the site, all the views have access to the variable $currentUser. However, when attempting to test my application, the variable is never bound, despite a user definitely being logged in.
class PagesControllerTest extends TestCase
{
public function setUp()
{
parent::setUp();
// This works, as halting the application and dumping the user manually demonstrate it as such.
Sentry::login(User::first());
}
public function testIndex()
{
$this->get('/');
$this->assertResponseOk();
}
}
However, this simply results in a stack-trace of errors...
ErrorException: Trying to get property of non-object (View: ~/Sites/laravel/app/views/layouts/application.blade.php) (View: ~/Sites/laravel/app/views/layouts/application.blade.php)
...
Caused by
ErrorException: Trying to get property of non-object (View: ~/Sites/laravel/app/views/layouts/application.blade.php)
...
Caused by
ErrorException: Trying to get property of non-object
The exact line this fails at is where the view tries to access the $currentUser variable.
If I use a view composer instead, like follows, it solves the problem in this instance - but I want the variable available in ALL views, not just the ones I specify, and I'd also like to know WHY this is occurring.
View::composer('layouts.application', function($view)
{
$view->with('currentUser', Sentry::currentUser());
});
I'm guessing you are doing View::share in app/start/global.php. This file is invoked by calling parent::setUp(), which is before you've done Sentry::login, and thus, $currentUser will be null. You should either find a way to delay the View::share (using a view composer is one way to do this) or just use Sentry::getUser() in your views.

PHP Mockery not calling mocked method from within mock

I've been trying to use Mockery to assert that a method is called from within another method, when an exception is thrown. So as an example:
public function testOtherMethodIsCalled() {
$client = m::mock('Client');
$client
->shouldReceive('getFoo')
->andThrow(new FooNotAvailableException);
$controller = m::mock('Controller[otherMethod]');
$controller
->shouldReceive('otherMethod')
->once();
$controller->setClient($client);
$controller->firstMethod();
}
Obviously the names have been simplified, but that's line for line identical in every other way to what I have. In the code, when FooNotAvailableException is caught I return the call to otherMethod().
The problem is that when run it, I get this error:
Mockery\CountValidator\Exception: Method otherMethod() from Controller should be called exactly 1 times but called 0 times.
That's because internally the original, unmocked otherMethod() is being called. If I were to call it from within the test, like this:
$controller->otherMethod();
The test passes.
Why is that, and how would I write the test for what I want to test?
It's hard to tell without the full code source, but i believe that this is what is occurring:
Your code:
$client = m::mock('Client');
$client
->shouldReceive('getFoo')
->andThrow(new FooNotAvailableException);
So far so good. No problem yet.
$controller = m::mock('Controller[otherMethod]');
$controller
->shouldReceive('otherMethod')
->once();
$controller->setClient($client);
$controller->firstMethod();
Now, we run into a problem. I'm assuming that the code under test is redirecting to another URL. When that occurs, you're going to instantiate another Controller. The Controller that you instantiated is not going to be the Controller that is instantiated by "m::mock('Controller[otherMethod]')". So, obviously the mocked instance won't ever receive the "otherMethod".
Depending on how your code under test is actually written, the proper way to test this may be to assert that a Redirect::to has been called from the function that handles the FooNotAvailableException.

PHPUnit with DBUnit: initial fixture fails to set up

I've created a database testing class for one of my modules. When I try to run a test case, the initial fixture is not set up - all the tables in the testing database are empty. Here is my getDataSet() method:
/**
* #return PHPUnit_Extensions_Database_DataSet_IDataSet
*/
public function getDataSet()
{
return $this->createFlatXMLDataSet(dirname(__FILE__).'/dataSets/initial.xml');
}
I found out that the getDataSet() method gets called, because when I create a syntax error in it, the test execution fails. But when I create errors in file initial.xml, nothing happens. It seems like the file initial.xml is not being parsed at all!
The path to the file should be OK, but I noticed that when I enter an invalid path, again, nothing happends. Apparently, method createFlatXMLDataSet() does not throw any exception when something is wrong. So now I don't have any clue of why it doesn't work :(
RESOLVED: It turned out that getDataSet() was not called, because I've overriden setUp() and didn't realize that I should call parent::setUp() from the overriden method body.
So the conclusion is: If you are overriding method setUp(), you always have to call parent::setUp().

Laravel 4 Unit Testing: "headers already sent" error when injecting mocks using App::instance

I'm new to Laravel and the concept of the IoC. I was following the great tutorials over a Nettuts (http://net.tutsplus.com/tutorials/php/testing-laravel-controllers/) and was able to successful test my controller. However I wanted to isolate the controller by mocking the database. As soon as I attempted to inject my mocked object into the IoC I get the following error:
Cannot modify header information - headers already sent by (output started at /Users/STRATTON/Dev/SafeHaven/vendor/phpunit/phpunit/PHPUnit/Util/Printer.php:172)
The line it's referring to outputs PHPUnit's buffer using the 'print' construct. Something is causing output to be sent before headers are set but I can't track down the problem.
I'm able to run all my tests successfully when the controller calls the real model and makes the database call. At the same time I'm able to mock the object successfully and exercise mock without error. But as soon as I attempt to inject the mocked object using App::instance() the error appears.
I've also tested this with PHPUnit's mocks and get the same results. Am I mocking the object properly? Do I have a problem with namespacing? Am I missing something that's outputting content?
Controller:
<?php namespace App\Controllers;
use App\Models\Repositories\ArticleRepositoryInterface;
class HomeController extends BaseController {
protected $articles;
public function __construct(ArticleRepositoryInterface $articles)
{
$this->articles = $articles;
}
public function index()
{
$articles = $this->articles->recent();
return \View::make('home.index')
->with('articles', $articles);
}
}
TestCase
<?php namespace Tests\Controllers;
class HomeControllerTest extends \TestCase {
public function testIndex()
{
$mocked = \Mockery::mock('App\\Models\\Repositories\\ArticleRepositoryInterface');
$mocked->shouldReceive('recent')->once()->andReturn('foo');
\App::instance('App\\Models\\Repositories\\ArticleRepositoryInterface', $mocked);
//$mocked->recent();
$this->call('GET', '/');
$this->assertResponseOk();
$this->assertViewHas('articles');
}
}
It actually a a bug on how exception is handled during running test case, this however has been fixed, just run composer update.
Answering my own question - The reason for the error is that some part of the code is causing a PHP error or an exception to be thrown.
In this case, the problem was an Exception thrown from the View. The View was expecting that the value returned by the method recent() was an Eloquent Collection (Illuminate\Database\Eloquent\Collection), or at least something that the View could iterate over.
THe HomeControllerTest::TestIndex method was mocking the object and when recent() was called it returned 'foo'. There's no way for the View to iterate over a string so it throws an exception. The two solutions are below, the later allowing the ability to test that the view received the correct object type.
$mocked->shouldReceive('recent')
->once()
->andReturn([]);
If you're having a similar issue, examine all the code being tested and make sure you're tests actually fulfill all the requirements/dependancies... or use TDD... something I should have done from the start and avoided this issue.

Categories