Mocked class not accepting instance of Carbon - unit-testing

I am trying to test a method with PhpUnit and Mockery. In the process of specifying a method should be called with arguments my test fails.
TEST:
$this->eventRepo = \Mockery::mock('Path\To\EventRepository');
$start = Carbon::createFromFormat('Ymd-H-i-s', '20141211-09-21-00');
$end = Carbon::createFromFormat('Ymd-H-i-s', '20141211-09-19-00');
$this->eventRepo
->shouldReceive('findEvent')
->withArgs([
$start,
$end,
'1',
'1234567891'
])
->andReturn($callEvent);
REAL CODE:
$start = Carbon::createFromFormat('Ymd-H-i-s', '20141211-09-20-00');
$end = Carbon::createFromFormat('Ymd-H-i-s', '20141211-09-20-00');
$event = $this->eventRepo->findEvent(
$start->subSeconds(60),
$end->addSeconds(60),
$id,
$number
);
ERROR FROM TEST:
Mockery\Exception\NoMatchingExpectationException: No matching handler found for EventRepo::findEvent(object(Carbon\Carbon), object(Carbon\Carbon), "1", "1234567891"). Either the method was unexpected or its arguments matched no expected argument list for this method
$this->eventRepo is a mocked in the test. The real code runs correctly. After the error displays, it, I guess var_dump()'s a instance of Carbon.
I have no idea what could be causing this. I tried googling it but not knowing what to google made that pretty worthless. Has anyone run into this before?

When using an object in with() or withArgs(), phpunit performs an === check. This means that it'll look for the exact same instance of the class, not just any instance of Carbon.
In this case, it means that findEvent() is receiving an instance of Carbon, but not the exact same instance that you have in the actual code.

Related

Is a Cache mock called more than once when browser-testing?

I'm trying to cover the following:
I'm using the following test code:
public function test_it_deletes_a_patient()
{
// ...
$cacheKey = vsprintf('%s.%s', [$this->doctorUser->id, 'backoffice.stats.patientsTotalCount']);
Cache::shouldReceive('has')->with($cacheKey)->once()->andReturn(false);
Cache::shouldReceive('increment')->with($cacheKey, -1)->once()->andReturn(true);
$response = $this->json('DELETE', route('patients.destroy', $this->patient), ['confirmation' => 'ELIMINAR']);
// ...
}
That triggers the following controller code:
public function destroy(Patient $patient, Request $request)
{
$this->authorize('delete', $patient);
$confirmation = $request->get('confirmation');
if ($confirmation != 'ELIMINAR') {
return response()->json(['success' => false]);
}
logger()->info("Deleting Patient Profile PATIENT_ID:[{$patient->id}]");
$patient->delete();
$this->updatePatientsCount(-1);
return response()->json(['success' => true]);
}
protected function updatePatientsCount($amount = 1)
{
$key = vsprintf('%s.%s', [auth()->user()->id, 'backoffice.stats.patientsTotalCount']);
if (Cache::has($key)) { // I want to mock for testing this
Cache::increment($key, $amount); // I want to mock for testing this
}
}
After test run I get:
alariva#trinsic:~/fimedi$ t --filter=test_it_deletes_a_patient
PHPUnit 7.3.1 by Sebastian Bergmann and contributors.
F 1 / 1 (100%)
Time: 6.53 seconds, Memory: 26.00MB
There was 1 failure:
1) Tests\Browser\Backoffice\PatientsTest::test_it_deletes_a_patient
Unable to find JSON fragment
["success":true]
within
[{"exception":"Mockery\\Exception\\NoMatchingExpectationException","file":"\/home\/alariva\/fimedi\/vendor\/mockery\/mockery\/library\/Mockery\/ExpectationDirector.php","line":92,"message":"No matching handler found for Mockery_0_Illuminate_Cache_CacheManager::has('2056e535e689ab723b3f44831b488f05f7fb8b90'). Either the method was unexpected or its arguments matched no expected argument list for this method\n\n","trace":[{"class":"App\\Http\\Middleware\\Language","file":"\/home\/alariva\/fimedi\/vendor\/laravel\/framework\/src\/Illuminate\/Pipeline\/Pipeline.php","function":"handle","line":151,"type":"->"},{"class":"Barryvdh\\Debugbar\\Middleware\\InjectDebugbar","file":"\/home\/alariva\/fimedi\/vendor\/laravel\/framework\/src\/Illuminate\/Pipeline\/Pipeline.php","function":"handle","line":151,"type":"->"},{"class":"Illuminate\\Auth\\Middleware\\Authenticate","file":"\/home\/alariva\/fimedi\/vendor\/laravel\/framework\/src\/Illuminate\/Pipeline\/Pipeline.php","function":"handle","line":151,"type":"->"},{"class":"Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse","file":"\/home\/alariva\/fimedi\/vendor\/laravel\/framework\/src\/Illuminate\/Pipeline\/Pipeline.php","function":"handle","line":151,"type":"->"},{"class":"Illuminate\\Cookie\\Middleware\\EncryptCookies","file":"\/home\/alariva\/fimedi\/vendor\/laravel\/framework\/src\/Illuminate\/Pipeline\/Pipeline.php","function":"handle","line":151,"type":"->"},{"class":"Il
What I interpret after a couple of tests, is that it looks like once I mock Cache it is being called by some middlewares before reaching the tested block, so since those called methods are not mocked, the test fails because it does not know what to answer for those middleware calls.
Imagine I could successfully mock all the calls before getting to the tested codeblock, I would be able to make it reach. But that's not the way to go over it.
How can I mock Cache and avoid failure due to previous Cache calls that I'm not testing?
EDIT: I realized after getting to a solution that this is a misleading question. My actual need was:
How can I successfully cover those lines?
Sidenote: if I try to disable middlewares ($this->withoutMiddleware();) I get an AccessDeniedHttpException
alariva#trinsic:~/fimedi$ t --filter=test_it_deletes_a_patient
PHPUnit 7.3.1 by Sebastian Bergmann and contributors.
F 1 / 1 (100%)
Time: 12.95 seconds, Memory: 24.00MB
There was 1 failure:
1) Tests\Browser\Backoffice\PatientsTest::test_it_deletes_a_patient
Unable to find JSON fragment
["success":true]
within
[{"exception":"Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException","file":"\/home\/alariva\/fimedi\/vendor\/laravel\/framework\/src\/Illuminate\/Foundation\/Exceptions\/Handler.php","line":201,"message":"This action is unauthorized.","trace":[{"class":"App\\Exceptions\\Handler","file":"\/home\/alariva\/fimedi\/vendor\/laravel\/framework\/src\/Illuminate\/Routing\/Pipeline.php","function":"render","line":83,"type":"->"},{"class":"Illuminate\\Foundation\\Exceptions\\Handler","file":"\/home\/alariva\/fimedi\/app\/Exceptions\/Handler.php","function":"render","line":65,"type":"->"},{"class":"Illuminate\\Foundation\\Exceptions\\Handler","file":
Maybe I can cherry-pick middlewares to disable?
I managed to cover the controller's method by encapsulating the custom Cache operation into a macro, so as to get the benefits of spliting into code units.
I moved my code into a macro (in the boot() of a service provider):
Cache::macro('incrementExisting', function($key, $amount) {
if (Cache::has($key)) {
Cache::increment($key, $amount);
}
return $this;
});
I refactored to use the macro
protected function updatePatientsCount($amount = 1)
{
$key = vsprintf('%s.%s', [auth()->user()->id, 'backoffice.stats.patientsTotalCount']);
Cache::incrementExisting($key, $amount);
}
I could get the desired coverage while I can still test the refactored code with unit testing.
Update I
Regarding the concern of handling many calls that are not mocked, I just learned from Adam Wathan that there exists shouldIgnoreMissing() and that would allow to use the Mocking approach for this case.
Update II
Write your tests first. When doing so it gets easier to avoid hard-to-test code.

Doctrine don't fine findBy PhpUnit

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.

call_user_func() expects parameter 1 to be a valid callback - Laravel unit testing

I am testing an API that returns a JOSN object, while running the following:
public function testBasicExample()
{
$response = $this->call('GET', 'sites/1/webmaster/totalstats?since=2014-01-01&until=2014-12-30');
}
getting error:
There was 1 error:
1) ExampleTest::testBasicExample ErrorException: call_user_func()
expects parameter 1 to be a valid callback, no array or string given
/var/www/html/laravel/app/facade/Webmaster.php:527
/var/www/html/laravel/app/helpers/WebmasterHelper.php:100
/var/www/html/laravel/app/controllers/WebmasterController.php:129
/var/www/html/laravel/app/routes.php:73
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Routing/Route.php:109
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1033
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Routing/Router.php:1001
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:775
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Application.php:745
/var/www/html/laravel/vendor/symfony/http-kernel/Symfony/Component/HttpKernel/Client.php:81
/var/www/html/laravel/vendor/symfony/browser-kit/Symfony/Component/BrowserKit/Client.php:327
/var/www/html/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Testing/ApplicationTrait.php:51
/var/www/html/laravel/app/tests/ExampleTest.php:16
FAILURES! Tests: 1, Assertions: 0, Errors: 1.
I just started laravel a few days ago so I might do some very basic mistakes. Please help. Thanks
Assuming that you're trying to send a GET request at that endpoint, you're using the wrong method.
Try this:
public function testBasicExample()
{
$response = $this->get('/sites/1/webmaster/totalstats?since=2014-01-01&until=2014-12-30');
}
Then you'll probably want to make use of one of Laravel's built-in JSON-testing methods: https://laravel.com/docs/5.6/http-tests#testing-json-apis
you might want to do this way
$response = $this->call('GET', 'sites/1/webmaster/totalstats', [
'since' => '2014-01-01',
'until' => '2014-12-30'
]);
input parameters will place as the 3rd parameters, you might give it a try.

Issue with $softDelete in model testing

I am using laravel-test-helper package by jeffrey way in laravel 4 to test my models. When I use
protected $softDelete = true;
in my model, the following assertion fails:
public function testMyModel()
{
$obj = Factory::create('Modelname');
Assert::equals(1,$obj->count());
}
and when I run the test without the $softDelete variable, it works fine.
Any clue to whats wrong?
This is the stack trace of the phpunit command, if it is of any help:
/var/www/project/vendor/way/laravel-test-helpers/src/Way/Tests/TestFacade.php:41
/var/www/project/vendor/way/laravel-test-helpers/src/Way/Tests/TestFacade.php:25
/var/www/project/vendor/way/laravel-test-helpers/src/Way/Tests/TestFacade.php:55
/var/www/project/app/tests/models/ModelTest.php:13
/var/www/project/app/tests/models/ModelTest.php:13
The Factory::create() will create random variables for your models. Because your are using 'softDelete' - there is a field in your table called 'deleted_at'. So the factory is putting data in there, and Laravel thinks the record is deleted, so ->count() returns 0.
You need to explicitly set Factory default for the deleted_at to null like this:
$obj = Factory::create('Modelname', array('deleted_at' => null));

Multiple calls to staticExpects on mocked objects

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"