5

I have a route in Laravel 7 that saves a file to a S3 disk and returns a temporary URL to it. Simplified the code looks like this:

Storage::disk('s3')->put('image.jpg', $file);
return Storage::disk('s3')->temporaryUrl('image.jpg');

I want to write a test for that route. This is normally straightforward with Laravel. I mock the storage with Storage::fake('s3') and assert the file creation with Storage::disk('s3')->assertExists('image.jpg').

The fake storage does not support Storage::temporaryUrl(). If trying to use that method it throws the following error:

This driver does not support creating temporary URLs.

A common work-a-round is to use Laravel's low level mocking API like this:

Storage::shouldReceive('temporaryUrl')
  ->once()
  ->andReturn('http://examples.com/a-temporary-url');

This solution is recommended in a LaraCasts thread and a GitHub issue about that limitation of Storage::fake().

Is there any way I can combine that two approaches to test a route that does both?

I would like to avoid reimplementing Storage::fake(). Also, I would like to avoid adding a check into the production code to not call Storage::temporaryUrl() if the environment is testing. The latter one is another work-a-round proposed in the LaraCasts thread already mentioned above.

Karl Hill
  • 12,937
  • 5
  • 58
  • 95
jelhan
  • 6,149
  • 1
  • 19
  • 35

3 Answers3

6

I had the same problem and came up with the following solution:

$fakeFilesystem = Storage::fake('somediskname');
$proxyMockedFakeFilesystem = Mockery::mock($fakeFilesystem);
$proxyMockedFakeFilesystem->shouldReceive('temporaryUrl')
    ->andReturn('http://some-signed-url.test');
Storage::set('somediskname', $proxyMockedFakeFilesystem);

Now Storage::disk('somediskname')->temporaryUrl('somefile.png', now()->addMinutes(20)) returns http://some-signed-url.test and I can actually store files in the temporary filesystem that Storage::fake() provides without any further changes.

korridor
  • 133
  • 2
  • 5
1

Re @abenevaut answer above, and the problems experienced in the comments - the call to Storage::disk() also needs mocking - something like:

    Storage::fake('s3');
    Storage::shouldReceive('disk')
        ->andReturn(
            new class()
            {
                public function temporaryUrl($path)
                {
                    return 'https://mock-aws.com/' . $path;
                }
            }
        );

    $expectedUrl = Storage::disk('s3')->temporaryUrl(
        'some-path',
        now()->addMinutes(5)
    );

    $this->assertEquals('https://mock-aws.com/some-path', $expectedUrl);
-1

You can follow this article https://laravel-news.com/testing-file-uploads-with-laravel, and mix it with your needs like follow; Mocks seem cumulative:

<?php

namespace Tests\Feature;

use Tests\TestCase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;

class ExampleTest extends TestCase
{
    public function testAvatarUpload()
    {
        $temporaryUrl = 'http://examples.com/a-temporary-url';

        Storage::fake('avatars');

        /*
         * >>> Your Specific Asserts HERE
         */
        Storage::shouldReceive('temporaryUrl')
            ->once()
            ->andReturn($temporaryUrl);

        $response = $this->json('POST', '/avatar', [
            'avatar' => UploadedFile::fake()->image('avatar.jpg')
        ]);


        $this->assertContains($response, $temporaryUrl);


        // Assert the file was stored...
        Storage::disk('avatars')->assertExists('avatar.jpg');

        // Assert a file does not exist...
        Storage::disk('avatars')->assertMissing('missing.jpg');
    }
}

Another exemple for console feature tests:

abenevaut
  • 768
  • 1
  • 9
  • 23
  • Did you tested this? Of course I tried to use `Storage::fake()` and `Storage::shouldReceive('temporaryUrl')` together in the same test. But it isn't working for me. In my tests it's not cumulative. Mocking seems to override the `Storage::fake()`. I'm facing errors like `Received Mockery_0_Illuminate_Filesystem_FilesystemManager::disk(), but no expectations were specified`. I'm using Laravel v7.25.0. – jelhan Sep 04 '20 at 13:04
  • I'll check during the week, i also saw https://laravel.com/api/5.8/Illuminate/Support/Facades/Storage.html#method_createMock the Storage facade allows to create a mock. Maybe that could solve the error message ? let me know if you try. – abenevaut Sep 06 '20 at 21:05
  • Also available for revision 7.x https://laravel.com/api/7.x/Illuminate/Support/Facades/Storage.html#method_createMock – abenevaut Sep 06 '20 at 21:06
  • Same problem with the temporayUrl in Unit Test. Still trying to get my head around it. Had the same "Received Mockery_0_Illuminate_Filesystem_FilesystemManager::disk(), but no expectations were specified" error. I got it working with `Storage::shouldReceive('disk')->andReturn(new class() { public function temporaryUrl() { return 'foo'; } });` – shopfreelancer Dec 07 '20 at 11:09