spatie / laravel-medialibrary

Associate files with Eloquent models
https://spatie.be/docs/laravel-medialibrary
MIT License
5.66k stars 1.07k forks source link

How to feature test? #623

Closed sandervanhooft closed 7 years ago

sandervanhooft commented 7 years ago

Thanks for sharing this great package!

What would be the recommended way of asserting that a file was stored in a feature test? I have tried the Laravel Storage::fake('media') facade with this package to no avail. It would be great to have this documented.

    /** @test */
    function example_test()
    {
        Storage::fake('media');
        $animation = factory(Animation::class)->create();
        $animation->addMedia(UploadedFile::fake()->image('thumbnail.jpg'))->toMediaCollection('thumbnails');
        $this->assertNotNull($animation->getFirstMediaPath('thumbnails'));
        Storage::disk('media')->assertExists($animation->getFirstMediaPath('thumbnails')); // fails here
    }

Result:

Unable to find a file at path [/Users/Sander/Desktop/devstreet/chroniql2/public/media/1/thumbnail.jpg].
Failed asserting that false is true.

The files appear to be stored on the expected path in production just fine though.

canvural commented 7 years ago

Why are you trying to test this? This package already tests that cover the thing you mention. You can take a look at them.

freekmurze commented 7 years ago

Our package already has test to make sure that this functionality works.

I'm assuming your test doesn't work because behind the screens Storage::fake is a real disk and expect real files and UploadFile::fake() is not a real file.

sandervanhooft commented 7 years ago

Hi @canvural , @freekmurze ,

I understand that these feature tests are already covered in the package (which is great). But I need to test the package's integration within my app - I was talking about my app's feature tests, not the package's, sorry for any confusion.

How would you recommend doing this, as Storage::fake() seems to not support this?

By the way, I experience the same issue with all medialibrary storage methods when combined with Storage::fake(), including addMediaFromUrl() (so no UploadFile::fake() here).

sandervanhooft commented 7 years ago

Addendum: the test I shared above is a stripped-down shareable example of what happens when I try to test this functionality. The actual tests are richer, testing real app behavior.

freekmurze commented 7 years ago

Haven't worked with Storage::fake myself just yet, so can't advise you there.

You could take the same approach as we did in our package tests. Create a real disk and manually make asserts against the filesystem.

sandervanhooft commented 7 years ago

I will give that a try, thanks!

On Wed, 26 Apr 2017 at 13:56 Freek Van der Herten notifications@github.com wrote:

Haven't worked with Storage::fake myself just yet, so can't advise you there.

You could take the same approach as we did in our package tests. Create a real disk and manually make asserts against the filesystem.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/spatie/laravel-medialibrary/issues/623#issuecomment-297378700, or mute the thread https://github.com/notifications/unsubscribe-auth/AG7dp8wxxQOYPcpB1TACE2D_qfgiCo6Hks5rzzDKgaJpZM4NImXs .

coppee commented 6 years ago

Hi @sandervanhooft ,

Did you finally find a way ? Thanks.

sandervanhooft commented 6 years ago

I'm on the road currently so I can't access my code the next two weeks...

This is what I think I did in the end:

I dropped the fake and used the normal mode instead, used a stub file, learned the path used by medialibrary to save it. The test now checks if the file is stored at that path. On Tue, 5 Sep 2017 at 18:31, Jérôme Coppée notifications@github.com wrote:

Hi @sandervanhooft https://github.com/sandervanhooft ,

Did you finally find a way ? Thanks.

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/spatie/laravel-medialibrary/issues/623#issuecomment-327230975, or mute the thread https://github.com/notifications/unsubscribe-auth/AG7dp8yLy7g6EBDlmRopR1uhtHxArsCNks5sfXcsgaJpZM4NImXs .

sandervanhooft commented 6 years ago

And ofcourse clean up the stub file afterward :) On Tue, 5 Sep 2017 at 20:11, Sander van Hooft info@sandervanhooft.nl wrote:

I'm on the road currently so I can't access my code the next two weeks...

This is what I think I did in the end:

I dropped the fake and used the normal mode instead, used a stub file, learned the path used by medialibrary to save it. The test now checks if the file is stored at that path. On Tue, 5 Sep 2017 at 18:31, Jérôme Coppée notifications@github.com wrote:

Hi @sandervanhooft https://github.com/sandervanhooft ,

Did you finally find a way ? Thanks.

— You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub https://github.com/spatie/laravel-medialibrary/issues/623#issuecomment-327230975, or mute the thread https://github.com/notifications/unsubscribe-auth/AG7dp8yLy7g6EBDlmRopR1uhtHxArsCNks5sfXcsgaJpZM4NImXs .

coppee commented 6 years ago

Of course :) Thanks for your message !

ivanvermeyen commented 6 years ago

In case anyone else is still struggling with testing file uploads using MediaLibrary. As noted above, when faking the 'media' storage, the uploaded files can't be found.

This basic test example works for me. I'm running this test in a package repo, using orchestral/testbench, but I suppose this should also work in a normal Laravel app. I'm using Laravel 5.5.

Instead of faking the storage disk, I just change the root path of the disk in the config.

I have this controller method to add a photo to a Post:

public function store(Post $post)
{
    request()->validate([
        'photo' => "required|image",
    ]);

    return $post->addMediaFromRequest('photo')->toMediaCollection('photos');
}

In the test I create a fake photo, send it via a POST request and then assert that it is added to the Post and that it exists in the filesystem. This also works with conversions.

/** @test */
public function a_photo_can_be_added_to_a_post()
{
    $this->withoutExceptionHandling();

    config()->set('filesystems.disks.media', [
        'driver' => 'local',
        'root' => __DIR__.'/../../temp', // choose any path...
    ]);

    config()->set('medialibrary.default_filesystem', 'media');

    $photo = \Illuminate\Http\Testing\File::image('photo.jpg');

    $response = $this->postJson($this->url(), [
        'photo' => $photo,
    ]);

    $response->assertStatus(200);

    $photos = $this->post->getMedia('photos');

    $this->assertCount(1, $photos);
    $this->assertFileExists($photos->first()->getPath());
    $this->assertFileExists($photos->first()->getPath('thumb'));
}

Note that the temp folder is not cleared automatically, so you should do that in the setUp probably.

sandervanhooft commented 6 years ago

Thank you for sharing!

I’d make sure the temp folder is clean both at tearDown() and setUp() (in case the testrun for whatever reason crashes and does not make it to tearDown()). On Fri, 10 Nov 2017 at 16:48, Ivan Vermeyen notifications@github.com wrote:

In case anyone else is still struggling with testing file uploads using MediaLibrary. As noted above, when faking the 'media' storage, the uploaded files can't be found.

This basic test example works for me. I'm running this test in a package repo, using orchestral/testbench https://github.com/orchestral/testbench, but I suppose this should also work in a normal Laravel app. I'm using Laravel 5.5.

Instead of faking the storage disk, I just change the root path of the disk in the config.

I have this controller method to add a photo to a Post:

public function store(Post $post){ request()->validate([ 'photo' => "required|image", ]); return $post->addMediaFromRequest('photo')->toMediaCollection('photos');}

In the test I create a fake photo, send it via a POST request and then assert that it is added to the Post and that it exists in the filesystem. This also works with conversions.

/* @test /public function a_photo_can_be_added_to_a_post(){ $this->withoutExceptionHandling(); config()->set('filesystems.disks.media', [ 'driver' => 'local', 'root' => DIR.'/../../temp', // choose any path... ]); config()->set('medialibrary.default_filesystem', 'media'); $photo = \Illuminate\Http\Testing\File::image('photo.jpg'); $response = $this->postJson($this->url(), [ 'photo' => $photo, ]); $response->assertStatus(200); $photos = $this->post->getMedia('photos'); $this->assertCount(1, $photos); $this->assertFileExists($photos->first()->getPath()); $this->assertFileExists($photos->first()->getPath('thumb'));}

Note that the temp folder is not cleared automatically, so you should do that in the setUp probably.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/spatie/laravel-medialibrary/issues/623#issuecomment-343508764, or mute the thread https://github.com/notifications/unsubscribe-auth/AG7dp79M8VmghW8zFxTcr1F3xI8rDntkks5s1HBcgaJpZM4NImXs .

ohnotnow commented 6 years ago

Just in case anyone else finds this, on the back of @ivanvermeyen 's code above - if you want to take advantage of Laravel's Storage::fake() so that your test uploads are cleaned up etc, you can try this :

// some test function
Storage::fake('images');
config()->set('filesystems.disks.images', [
    'driver' => 'local',
    'root' => Storage::disk('images')->getAdapter()->getPathPrefix(),
]);
// do some test code that triggers your upload, then if you want you can :
$this->assertFileExists($model->getFirstMedia('my-collection')->getPath());

Edit: oh, and if you're trying to test code that outputs a view that uses the getUrl() from a media item, you might need to over-ride the default LocalUrlGenerator too as it throws an exception if the URL doesn't seem to be public (as far as I can see anyway). So I made a fake url generator which just skipped the exception check :

<?php

namespace Tests\Feature;

use Spatie\MediaLibrary\UrlGenerator\LocalUrlGenerator;

class FakeStorageUrlGenerator extends LocalUrlGenerator
{
    protected function getBaseMediaDirectoryUrl() : string
    {
        if ($diskUrl = $this->config->get("filesystems.disks.{$this->media->disk}.url")) {
            return str_replace(url('/'), '', $diskUrl);
        }

        // conditional that throws exception was here

        return $this->getBaseMediaDirectory();
    }
}

Then in the test :

Storage::fake('images');
config()->set('filesystems.disks.images', [
    'driver' => 'local',
    'root' => Storage::disk('images')->getAdapter()->getPathPrefix(),
]);
config()->set('medialibrary.url_generator', 'Tests\Feature\FakeStorageUrlGenerator');
// now you can get your view without an exception being thrown
4unkur commented 2 years ago

@ohnotnow I might be wrong, but seems like provided solution stopped working after this commit: https://github.com/spatie/laravel-medialibrary/blob/1f4dabfb84a2f4cb795ed075e7a75a2daebfa549/src/Support/File.php

Finfo returns application/x-empty mimetype

danielzzz commented 2 years ago

hi,

is it possible to fake addMediaFromUrl()?

I've seen it uses fopen internally, so I tried to trick it with passing a local file, but it throws an exception if the path does not start with http.

My scenario is that I have some service that calls an external api, then gets a list of file URIs to download. This list is then passed to $model->addMediaFromUrl()

I can fake the api output, but how could I pass some fake url to addMediaFromUrl and expect it to add a file?

thanks, dan

paolocaccavo commented 2 years ago

Hi! If you absolutely need to use fake storage, you can replace assertExists with assertEquals, using the id and filename that should be generated:

$this->assertEquals('/storage/1/name-of-uploaded-file.jpg', $yourModel->getFirstMediaUrl('media_collection_name'));

danielzzz commented 2 years ago

thanks @paolocaccavo , but what I need is to be able to access $model->addMediaFromUrl within a service, not just test the url of the item

tontonsb commented 2 years ago

The main problem seems to be that the assertExists expects a path relative to the disk, but the getPath method return full path from the root of the actual filesystem.

The default path within the disk is $file->id.'/'.$file->file_name, but that can be overriden. It seems that to get the "actual" path we have to reach inside for the generator: $file->getUrlGenerator('')->getPathRelativeToRoot(). Here's a full example:

Storage::fake('media');

$response = $this->post(
    $uri,
    [
        ...$otherData,
        'attachments' => [
            UploadedFile::fake()->create('somefile.docx', 13),
            UploadedFile::fake()->create('otherfile.png', 17),
        ],
    ],
)->assertCreated();

$id = $response->getData()->data->id;

$obj = MyObject::find($id);
$media = $obj->getMedia('attachments');

$this->assertEquals(2, $media->count());

foreach ($media as $file)
    Storage::disk('media')->assertExists($file->getUrlGenerator('')->getPathRelativeToRoot());
mirkodzudzar commented 1 year ago

Storage::disk('media')->assertExists($file->getUrlGenerator('')->getPathRelativeToRoot());

Thanks :+1: That was the line of code that I was looking for!

In my case, it was even shortened

Storage::disk('media')->assertExists($file->getPathRelativeToRoot());

alpemreelmas commented 12 months ago

When I have done those steps, test works but I can see that all pictures are uploaded to the server and after test they are still there. Is there a solution for fix this ?

B2rana commented 2 months ago

Just in case anyone wants to write a test case for file upload in Laravel 11, can use the provided code sample below.

it('can update own profile', function () {
    $user = User::factory()->create();
    $header = $this->getAuthHeader($user); // set  Authorization Bearer Token

    Storage::fake('avatars');

    config()->set('filesystems.disks.media', [
        'driver' => 'local',
        'root' => Storage::disk('avatars')->path(''),
    ]);

    $data = [
        'first_name' => 'test',
        'last_name' => 'user',
        'avatar' => UploadedFile::fake()->image('avatar.jpg', 500, 500)->size(512)
    ];

    $response = $this->putJson( url('/api/v1/profile'), $data, $header );

    $response->assertStatus(Response::HTTP_OK)
        ->assertJson([
            'status' => 'success',
            'message' => 'Profile has been updated successfully.'
        ]);

    $this->assertDatabaseHas('users', [
        'first_name'  => $data['first_name'],
        'last_name'  => $data['last_name']
    ]);

    $this->assertFileExists($user->getFirstMedia( 'PUT_YOUR_COLLECTION_NAME_HERE' )->getPath());
});