How to test a route in Laravel that uses both `Storage::put()` and `Storage::temporaryUrl()`?
Asked Answered
L

3

6

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.

Leffert answered 5/4, 2020 at 13:43 Comment(0)
V
9

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.

Veda answered 2/4, 2021 at 22:52 Comment(0)
W
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);
Westwardly answered 23/3, 2021 at 13:6 Comment(0)
S
-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:

Summitry answered 31/8, 2020 at 9:40 Comment(4)
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.Leffert
I'll check during the week, i also saw laravel.com/api/5.8/Illuminate/Support/Facades/… the Storage facade allows to create a mock. Maybe that could solve the error message ? let me know if you try.Summitry
Also available for revision 7.x laravel.com/api/7.x/Illuminate/Support/Facades/…Summitry
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'; } });Catlett

© 2022 - 2024 — McMap. All rights reserved.