Issue testing Laravel Controller with Mockery | trying to get property of non-object - unit-testing

I'm very new to testing controllers and I'm running into a problem with a method(). I believe I'm either missing something in my test or my Controller / Repository is designed incorrectly.
The application I'm writing is basically one of those secure "one time" tools. Where you create a note, the system provides you with a URL, once that url is retrieved the note is deleted. I actually have the application written but I am going back to write tests for practice (I know that's backwards).
My Controller:
use OneTimeNote\Repositories\NoteRepositoryInterface as Note;
class NoteController extends \Controller {
protected $note;
public function __construct(Note $note)
{
$this->note = $note;
}
public function getNote($url_id, $key)
{
$note = $this->note->find($url_id, $key);
if (!$note) {
return \Response::json(array('message' => 'Note not found'), 404);
}
$this->note->delete($note->id);
return \Response::json($note);
}
...
I've injected my Note interface in to my controller and all is well.
My Test
use \Mockery as M;
class OneTimeNoteTest extends TestCase {
public function setUp()
{
parent::setUp();
$this->mock = $this->mock('OneTimeNote\Repositories\EloquentNoteRepository');
}
public function mock($class)
{
$mock = M::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
public function testShouldReturnNoteObj()
{
// Should Return Note
$this->mock->shouldReceive('find')->once()->andReturn('test');
$note = $this->call('GET', '/note/1234567890abcdefg/1234567890abcdefg');
$this->assertEquals('test', $note->getContent());
}
}
...
The error I'm getting
1) OneTimeNoteTest::testShouldReturnNoteObj
ErrorException: Trying to get property of non-object
/Users/andrew/laravel/app/OneTimeNote/Controllers/NoteController.php:24
Line 24 is in reference to this line found in my controller:
$this->note->delete($note->id);
Basically my abstracted repository method delete() obviously can't find $note->id because it really doesn't exist in the testing environment. Should I create a Note within the test and try to actually deleting it? Or would that be something that should be a model test? As you can see I need help, thanks!
----- Update -----
I tried to stub the repository to return a Note object as Dave Marshall mentioned in his answer, however I'm now receiving another error.
1) OneTimeNoteTest::testShouldReturnNoteObj
BadMethodCallException: Method Mockery_0_OneTimeNote_Repositories_EloquentNoteRepository::delete() does not exist on this mock object
I do have a delete() method in my repository and I know it's working when I test my route in the browser.
public function delete($id)
{
Note::find($id)->delete();
}

You are stubbing the note repository to return a string, PHP is then trying to retrieve the id attribute of a string, hence the error.
You should stub the repository to return a Note object, something like:
$this->mock->shouldReceive('find')->once()->andReturn(new Note());

Building upon Dave's answer, I was able to figure out what my problem is. I wasn't mocking the delete() method. I didn't understand the need to mock each individual method in my controller that would be called.
I just added this line:
$mock->shouldReceive('delete')->once()->andReturnNull();
Since my delete method is just deleting the note after it is found, I went ahead and mocked it but set it to return null.

Related

Laravel 5 Cannot Mock View

I'm trying to write a really basic test for one of my controllers
/**
* THIS IS MY CONTROLLER. $this->badge is a repository
* #return \Illuminate\Http\Response
*/
public function index()
{
return view('badges.index')->with([
'badges' => $badges = $this->badge->all()
]);
}
I'm using repositories which return Eloquent collections. My basic test is as follows:
public function testItShowsAllBadges()
{
// Arrange
//DISABLE AUTH MIDDLEWARE ON THIS ROUTE
$this->withoutMiddleware();
// MOCK THE REPO
$this->badge->shouldReceive('all')->andReturn(new Illuminate\Support\Collection);
// Act
$response = $this->action('GET', 'BadgeController#index');
// Assert
$this->assertResponseOk();
$this->assertInstanceOf('Illuminate\Support\Collection', $response->original->getData()['badges']);
$this->assertViewHas('badges');
}
This test fails with a message 'trying to get property of non-object'. This is because I do Auth::user()->something in the view.
So I need to mock the view but I don't know how. Can someone advise?
Other SO answers do not seem to work and just result in Exceptions being thrown in the test about methods not existing on the Mock. I have tried for example:
View::shouldReceive('make')
->once()
->andReturn(\Mockery::self())
Adding this before I call the route results in a 500 error 'Method Mockery_1_Illuminate_View_Factory::with() does not exist on this mock object'. I tried adding in
->shouldReceive('with')
->once()
->andReturn(\Mockery::self());
However this results in an Exception stating that getData() does not exist on this Mock Object. Even removing that assertion, assertViewHas('badges') fails saying the response was not a view.
Also I haven't understood if View::shouldReceive... is part of Arrange or Assert phase of the test?My understanding it is part of the arrange and should go before the $this->action(....)

PHPunit mock - call a function in a returned mock

I'm pretty new to phpunit and mocking, and I want to test a Listener in my symfony2 project, what is a kernel exception listener.
This is the class I want to test:
public function onKernelException(GetResponseForExceptionEvent $event)
{
$code = $event->getException()->getCode();
if($code == 403)
{
$request = $event->getRequest();
$session = $request->getSession();
$session->getFlashBag()->add('notice', 'message');
$session->set('hardRedirect', $request->getUri());
}
}
And first I just wanted to test, so nothing happens if the code is 404, this is the test I wrote:
public function testWrongStatusCode()
{
$exceptionMock = $this->getMock('Exception')
->expects($this->once())
->method('getCode')
->will($this->returnValue('404'));
$eventMock = $this->getMockBuilder('Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent')
->disableOriginalConstructor()
->getMock();
$eventMock->expects($this->once())
->method('getException')
->will($this->returnValue($exceptionMock));
//here call the listener
}
but PHPunit say, getCode function was never called.
You can't use "chaining" as you've tried. The reason is that methods getMock and will return different objects. That's why you lose your real mock object. Try this instead:
$exceptionMock = $this->getMock('\Exception');
$exceptionMock->expects($this->once())
->method('getCode')
->will($this->returnValue('404'));
Edit
Ok. The problem is you cannot mock getCode method because it's final and it's impossible to mock final and private methods with PHPUnit.
My suggestion is: just prepare an exception object you want, and pass it as returned value to event mock:
$exception = new \Exception("", 404);
(...)
$eventMock->expects($this->once())
->method('getException')
->will($this->returnValue($exception));
This is how I mock the getCode() function. It actually gets called from the ResponseInterface::getStatusCode() function, so that is what you need to mock:
$guzzle->shouldReceive('get')
->once()
->with(
$url
)
->andThrows(new ClientException(
"",
Mockery::mock(RequestInterface::class),
Mockery::mock(ResponseInterface::class, [
'getStatusCode' => 404,
]),
));
You can use mockery library with PHPUnit, which is great tool and makes life easier.
$exceptionMock = \Mockery::mock('GetResponseForExceptionEvent');
$exceptionMock->shouldReceive('getException->getCode')->andReturn('404');
Check out documentation for more... and I hope you will love it.

How to mock a CakePHP behavior for unit testing

I've just started with unit testing in CakePHP (yay!) and ran into the following challenge. Hope somebody can help me :-)
Situation
My model uses a Behavior to send changes to an API after saving it locally. I would like to fake all calls made to the API during the test (those will be tested seperately) to save load on the API server, and more important, not actually save the changes :-)
I'm using CakePHP 2.4.1.
What I've tried
Read the docs. The manual shows how to do this for Components and Helpers but not for Behaviors.
Google. What I've found:
A Google Group post which says it simply "isn't possible". I don't take no for an answer.
An article explaining how to mock an object. Comes pretty close.
The code from the article reads:
$provider = $this->getMock('OurProvider', array('getInfo'));
$provider->expects($this->any())
->method('getInfo')
->will($this->returnValue('200'));
It might be the wrong direction, but I think that might be a good start.
What I want
Effectively: A snippet of code to demo how to mock a behavior in a CakePHP Model for unit testing purposes.
Maybe this question will result in an addition of the CakePHP manual too as an added bonus, since I feel it's missing in there.
Thanks in advance for the effort!
Update (2013-11-07)
I've found this related question, which should answer this question (partly). No need to mock up the API, instead I can create a Behavior test that the model will use.
I'm trying to figure out what that BehaviorTest should look like.
Use the class registry
As with many classes, behaviors are added to the class registry using the class name as the key, and for subsequent requests for the same object loaded from the classregistry. Therefore, the way to mock a behavior is simply to put it in the class registry before using it.
Full Example:
<?php
App::uses('AppModel', 'Model');
class Example extends AppModel {
}
class TestBehavior extends ModelBehavior {
public function foo() {
throw new \Exception('Real method called');
}
}
class BehaviorExampleTest extends CakeTestCase {
/**
* testNormalBehavior
*
* #expectedException Exception
* #expectedExceptionMessage Real method called
* #return void
*/
public function testNormalBehavior() {
$model = ClassRegistry::init('Example');
$model->Behaviors->attach('Test');
$this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
$this->assertSame('TestBehavior', get_class($model->Behaviors->Test));
$this->assertSame(['foo' => ['Test', 'foo']], $model->Behaviors->methods());
$model->foo();
}
public function testMockedBehavior() {
$mockedBehavior = $this->getMock('TestBehavior', ['foo', 'bar']);
ClassRegistry::addObject('TestBehavior', $mockedBehavior);
$model = ClassRegistry::init('Example');
$model->Behaviors->attach('Test');
$this->assertInstanceOf('TestBehavior', $model->Behaviors->Test);
$this->assertNotSame('TestBehavior', get_class($model->Behaviors->Test));
$expected = [
'foo' => ['Test', 'foo'],
'bar' => ['Test', 'bar'],
'expects' => ['Test', 'expects'], // noise, due to being a mock
'staticExpects' => ['Test', 'staticExpects'], // noise, due to being a mock
];
$this->assertSame($expected, $model->Behaviors->methods());
$model->foo(); // no exception thrown
$mockedBehavior
->expects($this->once())
->method('bar')
->will($this->returnValue('something special'));
$return = $model->bar();
$this->assertSame('something special', $return);
}
}

Mocking DBSet, EF Model First

As said in the title, I follow Model First method. So my Model classes are Automatically generated. If I want mock the DBContext derived MyModelContainer which contain DBSets of entity classes. Read some where that in order to unit test, you need to change it to IDBSet. Whether its possible to do it especially in a class that gets auto generated when I do "Run Custom Tool" is one concern. But as of now I modified it.
But the real problem is: when I try to Stub MyModelContainer to return a mock generated from IDBSet. Rhino mock is firing an InvalidOperationException: "Invalid call, the last call has been used, or no call has been made(make sure that you are calling a virtual(C#)/Overridable(VB) method."
Here is my unit test code.
MyModelContainer dbMock = MockRepository.GenerateMock<MyModelContainer>();
IDBSet<Models.MyEntity> entityMock = MockRepository.GenerateMock<IDBSet<Models.MyEntity>>()
dbMock.Stub( x=>x.MyEntities ).Return( entityMock );
The last statement is triggering the exception. I tried using the fake implementation of IDBSet<> specified here, But no luck!
I use MVC 4, Rhino Mocks 3.6. Any help will be appreciated.
Update:
After some trials and research, I found a fix. I changed the code to:
MyModelContainer dbMock = MockRepository.GenerateMock<MyModelContainer>();
IDBSet<Models.MyEntity> entityMock = MockRepository.GenerateMock<IDBSet<Models.MyEntity>>()
//dbMock.Stub( x=>x.MyEntities ).Return( entityMock );
dbMock.MyEntities = entityMock;
Now the InvalidOperationException is gone.
The test fails only due to ExpectationViolationException which should be normal.
As for auto generated Model class, it is found out that editing the DbContext's T4 template (.tt extension) will do the trick. Thanks to Alan's Blog
But I want to know why the previous code didn't work. Anyone?
2 reasons are possible here:
MyEntites property of MyModelContainer is not virtual.
In that case Rhino Mock can't stub this property at all. Then dbMock.Stub(x=>x.MyEntities) will fail.
MyEntites property is virtual, but has both public getter and public setter.
Then notation dbMock.Stub(x=>x.MyEntities).Return(entityMock) is not allowed. You can see explanation e.g. here.
In both cases the right fix is exactly what you did: use dbMock.MyEntities = entityMock instead of dbMock.Stub(x=>x.MyEntities).Return(entityMock).
Here is an extension method for Substituting IDbSet (with NSubstitute) to return an IQueryable
public static DbSet<T> FakeDbSet<T>(this IQueryable<T> queryable) where T : class
{
DbSet<T> fakeDbSet = Substitute.For<DbSet<T>, IQueryable<T>>();
((IQueryable<T>)fakeDbSet).Provider.Returns(queryable.Provider);
((IQueryable<T>)fakeDbSet).Expression.Returns(queryable.Expression);
((IQueryable<T>)fakeDbSet).ElementType.Returns(queryable.ElementType);
((IQueryable<T>)fakeDbSet).GetEnumerator().Returns(queryable.GetEnumerator());
fakeDbSet.AsNoTracking().Returns(fakeDbSet);
return fakeDbSet;
}
Then you can now stub the DbContext like this:
var db = NSubstitute.Substitute.For<DataContext>();
var fakeResult = emptyCustomers.FakeDbSet();
db.Customers.Returns(fakeResult);
Here is an extension method for Stubing (with RhinoMocks) IDbSet to return an IQueryable
public static class RhinoExtensions
{
public static IDbSet<T> MockToDbSet<T>(this IQueryable<T> queryable) where T : class
{
IDbSet<T> mockDbSet = MockRepository.GenerateMock<IDbSet<T>>();
mockDbSet.Stub(m => m.Provider).Return(queryable.Provider);
mockDbSet.Stub(m => m.Expression).Return(queryable.Expression);
mockDbSet.Stub(m => m.ElementType).Return(queryable.ElementType);
mockDbSet.Stub(m => m.GetEnumerator()).Return(queryable.GetEnumerator());
return mockDbSet;
}
}
Then you can now stub the DbContext like this:
_db.Stub(p => p.Customers).Return(fakeCustomers.MockToDbSet());

Unit test custom doctrine repository

I have a custom entity repository. For example, it looks like this:
namespace Foo\Repository;
use Doctrine\ORM\EntityRepository;
class Article extends EntityRepository
{
public function findRecent($limit)
{
$qb = $this->createQueryBuilder('a');
$qb->andWhere('a.publishDate IS NOT NULL')
->orderBy('a.publishDate', 'DESC')
->setMaxResults($limit);
return $qb->getQuery()->getResult();
}
}
I want to test in this case:
There is an ORDER BY in "recent"
There is a limit
The entity must have a publish date
I do not want to validate the SQL output of the query builder, since Doctrine can change the SQL between different versions. That will break my unit test. Therefore, my idea was this:
Create a mock of my repository
Create a mock of the query builder
Make sure $this->createQueryBuilder('a') returns the mocked query builder
Test for method calls on the query builder
In code:
namespace FooTest\Repository;
use PHPUnit_Framework_TestCase as TestCase;
class ArticleRepositoryTest extends TestCase
{
protected $qb;
protected $repository;
public function setUp()
{
$this->qb = $this->getMockBuilder('Doctrine\ORM\QueryBuilder')
->disableOriginalConstructor()
->getMock();
$this->repository = $this->getMockBuilder('Foo\Repository\Article')
->disableOriginalConstructor()
->getMock();
$this->repository->expects($this->once())
->method('createQueryBuilder')
->with($this->equalTo('a'))
->will($this->returnValue($this->qb));
}
public function testFindRecentLimitsToGivenLimit()
{
$limit = '1';
$this->qb->expects($this->any())
->method('setMaxResults')
->with($this->equalTo($limit));
$this->repository->findRecent($limit);
}
public function testFindRecentOrdersByPublishDate()
{
$this->qb->expects($this->any())
->method('andWhere')
->with($this->equalTo('a.publishDate'), $this->equalTo('DESC'));
$this->repository->findRecent(1);
}
}
This findRecent() call however never calls createQueryBuilder internally. PhpUnit points out:
FooTest\Repository\ArticleRepositoryTest::testFindRecentLimitsToGivenLimit
Expectation failed for method name is equal to when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
I think I did something wrong in creating the repository mock. How can I make sure this approach works? Or if there is a better alternative, what is that?
It looks to me like you are mocking the Repository you are trying to test, so findRecent() is indeed mocked and will return null.
You should be able to use the real repository instance.
The solution I found to testing subclassed repositories is to add a call to setMethodsExcept() when building the mock.
So you would modify your code within setUp() above like so:
$this->repository = $this->getMockBuilder('Foo\Repository\Article')
->disableOriginalConstructor()
->setMethodsExcept([
// Insert any overridden/implemented functions here, in your case:
'findRecent',
])
->getMock();