Tuesday, September 4, 2018

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

Leave a Comment

I'm trying to cover the following:

Uncovered line

Resulting in Uncovered Method

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?

1 Answers

Answers 1

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.

  1. 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; }); 
  2. 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.

Test coverage result

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment