Search code examples
phpsymfonyuploadfunctional-testingapi-platform.com

Api Platform: how to test file upload with ApiTestCase


I have an endpoint, that allows file upload, everything works fine. Next thing is to cover the endpoint with proper functional test. And here's the problem - I can't pass the file to the client making the request.

My test class extends \ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase.

static::createClient() method creates an instance of ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client and these Client does not support file uploads.

Beacuse of implementing the Symfony\Contracts\HttpClient\HttpClientInterface which defines public function request(string $method, string $url, array $options = []): ResponseInterface; there's no place for passing files argument.

The allowed options in Client does not support files array.

Internaly it looks like this:

ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client::request passes to the internal kernelBrowser an empty array in place of files params (2nd array): $this->kernelBrowser->request($method, $resolvedUrl, [], [], $server, $options['body'] ?? null)

How do you test endpoints with file upload by extending Base class for functional API tests which is ApiTestCase?

Here's some code, to help you visualize the problem:

ApiResource definition in entity:

/**
 * @ApiResource(
 *     collectionOperations={
 *         "file_upload"={
 *             "method"="post",
 *             "controller"=FileUpload::class,
 *             "path"="/api/file-upload-endpoint",
 *             "deserialize"=false,
 *             "openapi_context"={
 *                 "requestBody"={
 *                     "content"={
 *                         "multipart/form-data"={
 *                             "schema"={
 *                                 "type"="object",
 *                                 "properties"={
 *                                     "file"={
 *                                         "type"="string",
 *                                         "format"="binary"
 *                                     }
 *                                 }
 *                             }
 *                         }
 *                     }
 *                 }
 *             }
 *         },
 *     },
 * )
 */

Test class (don't mind the instance of UploadedFile, it's just there, to show you, that it cannot be passed anywhere):

<?php

declare(strict_types=1);

namespace App\Tests\Api;

use \ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
use Symfony\Component\HttpFoundation\File\UploadedFile;

final class FileUploadTest extends ApiTestCase
{
    public function testFileUploadSuccessfully():void
    {
        $file = new UploadedFile(
            TESTS_PROJECT_DIR.'/tests/files/Small_sample_of_jet.jpg',
            'Small_sample_of_jet.jpg',
            'image/jpeg',
        );

        static::createClient()->request(
            'POST',
            '/api/file-upload-endpoint',
            [
                'headers' => [
                    'Content-Type' => 'multipart/form-data',
                ],
            ],
        );

        self::assertResponseIsSuccessful();
        self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
    }
}

And here is what i'm looking for:

<?php

declare(strict_types=1);

namespace App\Tests\Api;

use \ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;
use Symfony\Component\HttpFoundation\File\UploadedFile;

final class FileUploadTest extends ApiTestCase
{
    public function testFileUploadSuccessfully():void
    {
        $file = new UploadedFile(
            TESTS_PROJECT_DIR.'/tests/files/Small_sample_of_jet.jpg',
            'Small_sample_of_jet.jpg',
            'image/jpeg',
        );

        static::createClient()->request(
            'POST',
            '/api/file-upload-endpoint',
            [
                'headers' => [
                    'Content-Type' => 'multipart/form-data',
                ],
            ],
            [
                'file'=>$file
            ]
        );

        self::assertResponseIsSuccessful();
        self::assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
    }
}

When modyfing the vendor itself and passing the files to the Client::request and then to the kernelBrowser in place of 2nd empty array, everything works fine (I'm aware of breaking the contract, that's not the issue here ;)).

I'm thinking if there's missing feature of uploading files in ApiTestCase or I just can't find the solution.

Pls halp!

Api Platform version: 2.5.6

PS: I know i can use different client - test.client

$client = static::$kernel->getContainer()->get('test.client');

which is an instance of Symfony\Bundle\FrameworkBundle\KernelBrowser, the same that is used internally by the Api Platform's Client and that supports files array, but that's not the point of my question. I'd like to know how to do file upload with ApiTestCase.


Solution

  • Since the current latest release of api-platform/core (2.5.8) we are able to pass more parameters to kernelBrowser->request via the extra key. This also now includes files!

    Here is a very basic example of testing an image upload (implemented based on the official API Platform documentation):

    
    $file = new UploadedFile(
        'path/to/images/my_image.png',
        'my_image.png',
        'image/png',
    );
    
    $response = static::createClient()->request('POST', '/upload_image',
        [
            'headers' => ['Content-Type' => 'multipart/form-data'],
            'extra' => [
                'files' => [
                    'file' => $file,
                ],
            ],
        ],
    );