Search code examples
laraveltestingmockingautomated-testsmockery

Test Laravel Controller's try catch


I'm using Laravel 5.7 and have this controller

public function upload(Request $request)
{
    $fileData   = $request->getContent();
    $fileName   = uniqid().'.xlsx';

    try {
        // save file locally
        Storage::disk('local')->put($fileName, $fileData);

        // dispatch to queue
        AnyJob::dispatch($fileName);

        // insert in import_statuses table for status checking
        AnyModel::create([
            'job_id'  => $fileName
        ]);

    } catch (\Exception $e) {
        error_log($e);

        return response()->json([
            'error' => 'Could not save file or queue not found.'
        ], 500);
    }

    return response()->json([
        'status' => 'OK',
        'jobId'  => $fileName,
    ]);
}

And I'm trying to create a test that enters the catch, but I can't get it to work.

I already tried mocking Storage and expect methods disk and put to be called, but it always complains about other methods being called and not expected.

I also tried mocking AnyJob and AnyModel in many ways but that doesn't seem to affect the request made by $this->post.

This is my test method right now:

public function testUploadException()
{
    $xlsxFile = UploadedFile::fake()->create('test.xlsx', 100);

    // ignore Storage and Job real work
    \Illuminate\Support\Facades\Storage::fake();
    $this->withoutJobs();

    $mock = Mockery::mock('\App\AnyModel');
    $mock
        ->shouldReceive('create')->times(3)
        ->andThrow(new \Exception('any error'));

    $this->app->instance('\App\AnyModel', $mock);

    $response = $this
        ->withHeaders(['content-type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'])
        ->post('/api/upload', [$xlsxFile]);

    $response
        ->assertStatus(500)
        ->assertJson([
            'error' => 'Could not save file or queue not found.'
        ]);
}

And what it returns

root@3e5d84a4db42:/application/api# vendor/bin/phpunit 
PHPUnit 7.5.4 by Sebastian Bergmann and contributors.

....F                                                               5 / 5 (100%)

Time: 2.62 seconds, Memory: 20.00MB

There was 1 failure:

1) Tests\Feature\UploadTest::testUploadException
Expected status code 500 but received 200.
Failed asserting that false is true.

/application/api/vendor/laravel/framework/src/Illuminate/Foundation/Testing/TestResponse.php:133
/application/api/tests/Feature/UploadTest.php:50

FAILURES!
Tests: 5, Assertions: 8, Failures: 1.

Solution

  • The main reason this isn't going to work is because you're not resolving the Model from the IoC e.g. using app() or dependency injection. This will work with facades but not Eloquent models.

    You can change the controller method definition to the following to resolve the class from the container:

    public function upload(Request $request, AnyModel $anyModel)
    

    Then your call to create would be:

    $anyModel->create([
        'job_id'  => $fileName
    ]);
    

    There are also a couple of issues in your test as well:

    1. The fully qualified name for the class is 'App\AnyModel' not '\App\AnyModel'. I would suggest using ::class instead of using a string i.e. AnyModel::class (make sure you've imported the class).
    2. You only seem to be calling create() once so having ->times(3) will throw an error.