I'm novice about phpunit.
I use this snippet to mock my EntityManager
$emMock = $this->getMock('\Doctrine\ORM\EntityManager',
array('getRepository', 'getClassMetadata', 'persist', 'flush'), array(), '', false);
$emMock->expects($this->any())
->method('getRepository')
->will($this->returnValue(new \it\foo\Entity\File()));
$emMock->expects($this->any())
->method('persist')
->will($this->returnValue(null));
$emMock->expects($this->any())
->method('getClassMetadata')
->will($this->returnValue((object) array('name' => 'aClass')));
$emMock->expects($this->any())
->method('flush')
->will($this->returnValue(null));
When I run my test I have this error
Error: Call to undefined method it\foo\Entity\File::findBy()
How can I mock this method?
If you look at your code, you will see, that at least one line of it calls getRepository() and uses the result to apply function findBy() on it. This is a very standard behavior of a Doctrine2 program.
You are mocking only the EntityManager - you have the mock in the variable $emMock. One of the (mocked) functions, getRepository() returns an object of class \it\foo\Entity\File, which you create in line 5.
I suppose that class \it\foo\Entity\File doesn't implement the same interface as a Doctrine2 repository, at least it obviously doesn't implement findBy(), so the error message occurs.
To solve this problem, you need to replace the return value of the mock function for getRepository with either e real Repository (which is normally not what you want in a unit test) or another mock:
$repoMock = $this->getMock('Doctrine\ORM\EntityRepository', [], [], '', false);
$emMock->expects($this->any())
->method('getRepository')
->will($this->returnValue($repoMock);
Most likely you have to mock some of the functions in the Repository as well, for example findBy() which may return the list of entries you want your test to work with.
Related
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.
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.
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);
}
}
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());
I am tring to write test cases for my controller in cakephp, so far i have managed to mock the controller and include the auth component which is used in my controller function. the problem is it seems that i can call staticExpects only once, which means that i can define a return value for only one function call, i don't want that, i need to call staticExpects more than once within the same test case.
Here is a part of my code.
$this->TasksController = $this->generate('Tasks', array(
'components' => array('Session','Auth' => array('User'), ) ));
$this->TasksController->Auth->staticExpects($this->any())
->method('User')
->with('userID')
->will($this->returnValue(224));
$this->TasksController->Auth->staticExpects($this->any())
->method('User')
->with('accID')
->will($this->returnValue('some ID here'));
whenever i do this and run the test it gives me this error
Expectation failed for method name is equal to when invoked zero or more times
Parameter 0 for invocation AuthComponent::user('userID') does not match expected value.
Failed asserting that two strings are equal.
Please help :)
You have to specify when the static methods are called using $this->at(index).
$this->TasksController->Auth->staticExpects($this->at(1))
->method('user')
->with('userID')
->will($this->returnValue(224));
$this->TasksController->Auth->staticExpects($this->at(2))
->method('user')
->with('accID')
->will($this->returnValue('some ID here'));
If you're not sure when they are called try each expectation one by one until the error messages will give you what is called
--- Expected
+++ Actual
## ##
-'userID'
+'accID'
One last thing, the correct method name is "user" and not "User"