8

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?

alariva
  • 2,051
  • 1
  • 22
  • 37

1 Answers1

3

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


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.

alariva
  • 2,051
  • 1
  • 22
  • 37