Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Api Tests & Assertions

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Time to test our API! When someone uses our API for real, they'll use some sort of HTTP client - whether it be in JavaScript, PHP, Python, whatever. So, no surprise that to test our API, we'll do the exact same thing. Create a client object with $client = self::createClient().

... lines 1 - 6
class CheeseListingResourceTest extends ApiTestCase
{
public function testCreateCheeseListing()
{
$client = self::createClient();
... lines 12 - 14
}
}

This creates a, sort of, "fake" client, which is another feature that comes from the API Platform test classes. I say "fake" client because instead of making real HTTP requests to our domain, it makes them directly into our Symfony app via PHP... which just makes life a bit easier. And, side note, this $client object has the same interface as Symfony's new http-client component. So if you like how this works, next time you need to make real HTTP requests in PHP, try installing symfony/http-client instead of Guzzle.

Making Requests

Let's do this! Make a request with $client->request(): make a POST request to /api/cheeses.

How nice is that? We're going to focus our tests mostly on asserting security stuff. Because we haven't logged in, this request will not be authenticated... and so our access control rules should block access. Since we're anonymous, that should result in a 401 status code. Let's assert that! $this->assertResponseStatusCodeSame(401).

... lines 1 - 8
public function testCreateCheeseListing()
{
... lines 11 - 12
$client->request('POST', '/api/cheeses');
$this->assertResponseStatusCodeSame(401);
}
... lines 16 - 17

That assertion is not part of PHPUnit: we get that - and a bunch of other nice assertions - from API Platform's test classes.

Let's try this! Run the test:

php bin/phpunit

Deprecation Warnings?

Oh, interesting. At the bottom, we see deprecation warnings! This is a feature of the PHPUnit bridge: if our tests cause deprecated code to be executed, it prints those details after running the tests. These deprecations are coming from API Platform itself. They're already fixed in the next version of API Platform... so it's nothing we need to worry about. The warnings are a bit annoying... but we'll ignore them.

Missing symfony/http-client

Above all this stuff... oh... interesting. It died with

Call to undefined method: Client::prepareRequest()

What's going on here? Well... we're missing a dependency. Run

composer require symfony/http-client

API Platform's testing tools depend on this library. That "undefined" method is a pretty terrible error...it wasn't obvious at all how we should fix this. But there's already an issue on API Platform's issue tracker to throw a more clear error in this situation. It should say:

Hey! If you want to use the testing tools, please run composer require symfony/http-client

That's what we did! I also could have added the --dev flag... since we only need this for our tests... but because I might need to use the http-client component later inside my actual app, I chose to leave it off.

Ok, let's try those tests again:

php bin/phpunit

Content-Type Header

Oooh, it failed! The response contains an error! Oh...cool - we automatically get a nice view of that failed response. We're getting back a

406 Not acceptable

In the body... reading the error in JSON... is not so easy... but... let's see, here it is:

The content-type application/x-www-form-urlencoded is not supported.

We talked about this earlier! When we used the Axios library in JavaScript, I mentioned that when you POST data, there are two "main" ways to format the data in the request. The first way, and the way that most HTTP clients use by default, is to send in a format called application/x-www-form-urlencoded. Your browser sends data in this format when you submit a form. The second format - and the one that Axios uses by default - is to send the data as JSON.

Right now... well... we're not actually sending any data with this request. But if we did send some data, by default, this client object would format that data as application/x-www-form-urlencoded. And... looking at our API docs, our API expects data as JSON.

So even though we're not sending any data yet, the client is already sending a Content-Type header set to application/x-www-form-urlencoded. API Platform reads this and says:

Woh, woh woh! You're trying to send me data in the wrong format! 406 status code to you!

The most straightforward way to fix this is to change that header. Add a third argument - an options array - with a headers option to another array, and Content-Type set to application/json.

... lines 1 - 8
public function testCreateCheeseListing()
{
... line 11
$client->request('POST', '/api/cheeses', [
'headers' => ['Content-Type' => 'application/json']
]);
... line 15
}
... lines 17 - 18

Ok, try the tests again:

php bin/phpunit

This time... 400 Bad Request. Progress! Down below... we see there was a syntax error coming from some JsonDecode class. Of course! We're saying that we're sending JSON data... but we're actually sending no data. Any empty string is technically invalid JSON.

Add another key to the options array: json set to an empty array.

... lines 1 - 8
public function testCreateCheeseListing()
{
... line 11
$client->request('POST', '/api/cheeses', [
... line 13
'json' => [],
]);
... line 16
}
... lines 18 - 19

This is a really nice option: we pass it an array, and then the client will automatically json_encode that for us and send that as the body of the request. It gives us behavior similar to Axios. We're not sending any data yet... because we shouldn't have to: we should be denied access before validation is executed.

Let's try that next! We'll also talk about a security "gotcha" then finish this test by creating a user and logging in.

Leave a comment!

64
Login or Register to join the conversation

In my case v2.5.3 all I had to do was:

`

public function testCreatePost()
{

$client = self::createClient();
$client->request('POST', '/api/posts');
self::assertResponseStatusCodeSame(401);

}

`

Seems it was updated by api platform, authentication should be the first check before anything.

1 Reply

Yep, ApiPlatform has changed a some things in its latest version. Thanks for sharing it!

Reply

Hello,

FYI, to be able to run the test properly I had to modify your Client::request method like this:


...
`
// line 95
        foreach ($options['headers'] as $key => $value) {
            [$key, $value] = explode(':', $value, 2);
            $value = trim($value);
            if ('Content-Type' === $key) {
                $server['CONTENT_TYPE'] = $value ?? '';

                continue;
            }
            $server['HTTP_'.strtoupper(str_replace('-', '_', $key))] = $value ?? '';
        }
`
...
1 Reply

In my case, I had to tweak your code even further.


        foreach ($options['headers'] as $key => $value) {
            if (is_array($value) && 'accept' === $key) {
                $value = join(',', $value);
            }
            [$key, $value] = explode(':', $value, 2);
            $value = trim($value);
            if ('Content-Type' === $key) {
                $server['CONTENT_TYPE'] = $value ?? '';

                continue;
            }
            $server['HTTP_'.strtoupper(str_replace('-', '_', $key))] = $value ?? '';
        }
Reply

Hey Julien,

Thank you for sharing your solution with others!

Cheers!

Reply

Hey Rakodev

Sorry but I don't fully get the reason of that change. Could you tell me what error are you getting when running the test just as shown in the video?

Cheers!

Reply

Hi MolloKhan,

I gave the right headers but when I run the test it didn't get it, so I got this error:

<blockquote>
2019-08-27T14:05:44+00:00 [error] Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException: "The content-type "application/x-www-form-urlencoded" is not supported. Supported MIME types are "application/ld+json", "application/json", "text/csv", "application/hal+json"." at /application/vendor/api-platform/core/src/EventListener/DeserializeListener.php line 128
</blockquote>

The $options['headers'] became like this:


array(2) {
  [0]=>
  string(30) "Content-Type: application/json"
  [1]=>
  string(27) "accept: application/ld+json"
}

So the final array looks like this before I did the changes:


array(2) {
  ["HTTP_0"]=>
  string(1) "C"
  ["HTTP_1"]=>
  string(1) "a"
}

FYI I don't use your source code in my project.
Here is my actual symfony.lock file:
<a href="https://gist.github.com/rakodev/2b80a2efbdddea8895e1bcb1a7f524e1&quot;&gt;https://gist.github.com/rakodev/2b80a2efbdddea8895e1bcb1a7f524e1&lt;/a&gt;

1 Reply

Hey Rakodev

I found the reason of this problem. In the latest version of symfony/http-client (4.3.4) it's a change in the way that headers are parsed, you can see it here (https://github.com/symfony/http-client/commit/92d8add2747e1f1c988550b19ebedf4a9b759450#diff-c31da22ee309f1a4c12290b3965480d2 ). So, the code that Ryan copied from ApiPlatform future version needs to change as well.
We have 2 options:
A) Use symfony/http-client 4.3.3 version or
B) Adjust the code as you mentioned above

Cheers!

Reply

Hey MolloKhan ,

Thank you for your deep dive into the problem.
Interesting that http-client had made a so big change between 4.3.3 and 4.3.4, I thought that the 3rd one was mainly for bug/security fixes.

Reply

NP man!

Yeah, probably they didn't realize the impact of the change or they didn't have any other alternative. Who knows...

Reply
Sakshi G. Avatar
Sakshi G. Avatar Sakshi G. | posted 1 year ago

For anyone getting Error in Evaluate() method of arraysubset inside src/ApiPlatform/Test/

Check your api version if it is greater than 2.4 using
composer show api-platform/core
instead of pasting ApiPlatform inside App just

Use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;

paste the above use statement above cheeselistingresource.php inside tests

if you get an error in self::container inside cheeselistingresource.php
use

$container= static::getContainer();

Reply

Thanks for another tip Sakshi G. :).

1 Reply
Gediminas N. Avatar
Gediminas N. Avatar Gediminas N. | posted 2 years ago

Hey!

I did everything like in this video, even re-did whole project because of some unkown error while trying to test testCreateCheeseListing() function, and I am still getting some error that I can't figure out why it is happening.. I can't progress in this tutorial without proper working phpunit

This is what I get when I try to run php bin/phpunit : https://pastebin.com/4qZ2G7gd

Reply

Hey Gediminas N.

I see you have phpunit7.5 however in course code we use phpunit8.2 probably that was a reason, you can safely change signature of method App\ApiPlatform\Test\Constraint\ArraySubset::evaluate() to match parent method if you want to keep phpunit 7.5 or upgrade it to 8.2 as Ryan did in chapter 10 =)

Cheers

Reply
akincer Avatar
akincer Avatar akincer | posted 2 years ago | edited

I know this will shock you, but I have another error. NOTE: Using docker and followed your instructions in part 3 to setup the test environment which everything seems to be working as expected there.

1) App\Tests\Functional\CheeseListingResourceTest::testCreateCheeseListing<br />Twig\Error\RuntimeError: An exception has been thrown during the rendering of a template ("Environment variable not found: "DATABASE_TEST_URL" in . (which is being imported from "C:\Users\ack\PhpstormProjects\API-Platform-Training2\config/routes/api_platform.yaml"). Make sure there is a loader supporting the "api_platform" type.").

So I decided to check:

`C:\Users\ack\PhpstormProjects\API-Platform-Training2>symfony console debug:container --env-vars --env=test

Symfony Container Environment Variables
=======================================


Name Default value Real value


APP_SECRET n/a "$ecretf0rt3st"
CORS_ALLOW_ORIGIN n/a "^https?://localhost(:[0-9]+)?$"
DATABASE_TEST_URL n/a "mysql://root:password@127.0.0.1:32769/main?sslmode=disable&charset=utf8mb4"


// Note real values might be different between web and CLI.`

And in phpunit.xml.dist:

<server name="APP_ENV" value="test" force="true" />

So where are common places to check to see what I did wrong?

Reply

Hey @Aaron Kincer!

Hmm. If you download the code for this tutorial, there is no DATABASE_TEST_URL that's being used. However, if you downloaded the course code from the next tutorial - https://symfonycasts.com/screencast/api-platform-extending - then we DO use a DATABASE_TEST_URL env var.

That variable can be set in 2 different ways:

A) If you use the same Docker setup as the api-platform-extending tutorial, then you will have a mysql container called database_test. If you're using the Symfony binary's web server, then this will expose a DATABASE_TEST_URL env var (assuming docker-compose is running).

B) Alternatively, you can manually set DATABASE_TEST_URL in your .env.test file. If you downloaded the api-platform-extending code, then the .env.test already defaults this variable to equal DATABASE_URL (so that, by default, if you're not using the Docker setup, we just re-use your one database connection for both environments).

Let me know if this helps! You're still, kind of, "mixing" different projects together if I understand correctly. Is that correct?

Cheers!

Reply
akincer Avatar
akincer Avatar akincer | weaverryan | posted 2 years ago | edited

Oh wait. I get it now. I added this to .env.test and it fixed it:

DATABASE_TEST_URL=$DATABASE_URL

Reply

Hey @ Aaron Kincer!

Nice work! But, this might just mean that you're using your normal database also for your tests (which is fine, but not idea).

The reason that DATABASE_TEST_URL is not being exposed when you run your tests is that you need to execute php through the Symfony binary so it can inject the env vars. Try this (which we do in the extending tutorial):


symfony php bin/phpunit

Cheers!

Reply
akincer Avatar

I sort of figured that was what was happening. Makes sense. Took that back out and used the binary to execute php and of course it works. Great way to tie in where you mentioned in a training course the difference between using the symfony binary to do something vs just doing it. Thanks!

1 Reply
akincer Avatar
akincer Avatar akincer | weaverryan | posted 2 years ago | edited

The only things I grabbed from the other tutorial was the docker-compose.yaml setup as seen here:

`version: '3.7'
services:

database:
    image: 'mariadb:10.5.5'
    environment:
        MYSQL_ROOT_PASSWORD: password
        MYSQL_DATABASE: main
    ports:
        # To allow the host machine to access the ports below, modify the lines below.
        # For example, to allow the host to connect to port 3306 on the container, you would change
        # "3306" to "3306:3306". Where the first port is exposed to the host and the second is the container port.
        # See https://docs.docker.com/compose/compose-file/#ports for more information.
        - '3306'
database_test:
    image: 'mariadb:10.5.5'
    environment:
        MYSQL_ROOT_PASSWORD: password
    ports:
        # To allow the host machine to access the ports below, modify the lines below.
        # For example, to allow the host to connect to port 3306 on the container, you would change
        # "3306" to "3306:3306". Where the first port is exposed to the host and the second is the container port.
        # See https://docs.docker.com/compose/compose-file/#ports for more information.
        - '3306'

`

and doctrine.yaml under the test config folder:

`# config/packages/test/doctrine.yaml
doctrine:
dbal:

url: '%env(resolve:DATABASE_TEST_URL)%'`

And as I showed above the env for the test environment is indeed exposed. Maybe there's somewhere else I need to define something? Because it looks to me like I have option A going on but something somewhere is missing it.

Reply
David B. Avatar
David B. Avatar David B. | posted 2 years ago

Is it possible to tie into the router service to generate routes for the test cases?

$client->request('GET', ROUTE_SERVICE);

Reply

Hey David B.

What you mean by "tie into the router service"? If you want to add some test routes, I think you can create a routing.yaml file inside config/routes/test so those routes only exist for the test environment

Cheers!

Reply
David B. Avatar
David B. Avatar David B. | MolloKhan | posted 2 years ago | edited

Rather than having to type in '/api/section_items' every time
$client->request('POST', '/api/section_items')
Is it possible to use the router service to generate the route ie:
$client->request('POST', $router->getRoute(SectionItem::class))

Reply

Ah, I understand now. You can use the routing service but you'd still type the *name* of the route, for example "api_users".

Reply
Gaetano S. Avatar
Gaetano S. Avatar Gaetano S. | posted 3 years ago

Hi,

after the suggestion of ryan to switch ahead to chapter 19&20, I realized that I need to start from chapter 10 because I have to install some libraries (test-pack, symfony/phpunit-bridge, symfony/broser-kit & css-selector, http-client). Actually I have this problem:

Testing Project Test Suite
E 1 / 1 (100%)

Time: 221 ms, Memory: 20.00 MB

There was 1 error:

1) App\Tests\Functional\CheeseListingResourceTest::testCreateCheeseListing
LogicException: You cannot create the client used in functional tests if the "framework.test" config is not set to true.

/var/www/html/vendor/api-platform/core/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php:62
/var/www/html/tests/Functional/CheeseListingResourceTest.php:16

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

I checked the framework.yaml and the test variable is true
Could you help me to resolve it?
Thanks a lot

Reply
Gaetano S. Avatar

I tried to remove symfony/test-pack, symfony/phpunit-bridge, symfony/broser-kit & css-selector, then reinstalling only test-pack after removing the vendor folder and a composer cache-clear. Same problem :(

Reply

Hey Gaetano S.

Can you double check that you're indeed hitting the test environment on your tests? Check your phpunit.xml.dist file
Another thing to check is the framework.yaml file but the one inside config/parameters/test. Or, you can execute bin/console debug:config framework --env=test -vvv and look for the "test" parameter

Let me know if you still have problems. Cheers!

Reply
Gaetano S. Avatar
Gaetano S. Avatar Gaetano S. | MolloKhan | posted 3 years ago | edited

Hi,
first check:


<php>
    <ini name="error_reporting" value="-1" />
    <server name="APP_ENV" value="test" force="true" />
    <server name="SHELL_VERBOSITY" value="-1" />
    <server name="SYMFONY_PHPUNIT_REMOVE" value="" />
    <server name="SYMFONY_PHPUNIT_VERSION" value="8.5.8" />
</php>
<testsuites>
    <testsuite name="Project Test Suite">
        <directory>tests</directory>
    </testsuite>
</testsuites>

second check (do you mean config/packages/test?):


framework:
     test: true
     session:
         storage_id: session.storage.mock_file

Thanks for your help

Reply

Hmm, this is interesting because everything looks correct.

I think I need to see your test case to see if I can spot something odd. Another thing to try out is dump the environment inside a test case, just to double check that you're indeed hitting the test environment (by looking at your config I'm positive it's hitting the test environment but just in case)

Another thing you can try is to remove the cache manually and try again rm var/cache/*

Reply
Gaetano S. Avatar
Gaetano S. Avatar Gaetano S. | MolloKhan | posted 3 years ago | edited

Hi,
I tried to remove the cache manually but nothing happened.
This is my test case


namespace App\Tests\Functional;

use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;

class CheeseListingResourceTest extends ApiTestCase
{
     public function testCreateCheeseListing(): void
     {
        $client = self::createClient();
        $client->request('POST', '/api/cheeses');
        $this->assertResponseStatusCodeSame(401);
        //        $this->assertEquals(42, 42);
     }
}

I hope it's useful to understand. thanks again

Reply

Hey Gaetano,

It looks like you're extending the wrong class, the class name is correct, but the namespace is not. You need use App\ApiPlatform\Test\ApiTestCase; instead of use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase; that you have. In the video, I see we're extending exactly App\ApiPlatform\Test\ApiTestCase, I suppose PhpStorm autocompleted the wrong namespace for you :)

I hope this helps!

Cheers!

Reply
Gaetano S. Avatar
Gaetano S. Avatar Gaetano S. | Victor | posted 2 years ago

Hi,

After fixing the error about the wrong class, I have still the error. I fall always inside the try-catch of ApiTestCase -> throw new \LogicException('You cannot create the client used in functional tests if the "framework.test" config is not set to true.');
I don't understand why.

Reply

Hey Gaetano S.!

Hmm. So this happens because your container is missing a test.api_platform.client service. If you're using API Platform 2.5 (and so, are not using our manually copied test classes that we use in this app, since we are trying to use this API Platform 2.5 feature in a 2.4 app), then this service should be registered automatically for you. Well, it's registered for you automatically, as long as you have this config: https://github.com/symfony/recipes/blob/ec008e83cf1afbbf4a6cd93109fbd83088906d93/symfony/framework-bundle/4.2/config/packages/test/framework.yaml#L2

If you're using our copied classes, then you need to add this service manually. We do that right here -https://symfonycasts.com/screencast/api-platform-security/backport-api-tests#registering-the-new-test-client-service - notice the public: true is important.

Let me know if this helps!

Cheers!

Reply
Gaetano S. Avatar

Hi,
I'm trying to do this. I have the version 2.5, so I don't need to do anything. Just creating CheeseListingResourceTest and using ApiTestCase of Apiplatform\Core\Bridge\Symfony\Test, is this?. And I have test:true inside the test/framework.yaml. But I have always the same LogicException error. It's incredibly strange. I don't understand where I missed something :(.

Reply

Hi again Gaetano S.!

Hmmm. Let's try a few commands to see what the status of your services is:


# does this work and show you the service?
php bin/console debug:container --env=test test.client

# I'm guessing this will fail. And if so, that's the mystery: why isn't this service added?
php bin/console debug:container --env=test test.api_platform.client

# look for "test.client.parameters" - do you see it? It will probably be set to an empty array, but it SHOULD be present
php bin/console debug:container --env=test --parameters

Also check your config/bundles.php file: which appears first? FrameworkBundle or ApiPlatformBundle? Normally, the order here does not matter. But I noticed that ApiPlatform takes a little shortcut that requires FrameworkBundle to be first. It ALWAYS is, unless someone manually changed it, which is why that's fine. But it's possible that this is your problem.

Let me know what you find out!

Cheers!

Reply
Gaetano S. Avatar

Hi,
Did you have the possibility to check my answer about the checks to do? Thanks for your help

Reply
Gaetano S. Avatar

Hi,
some pictures to show you infos you need -> https://drive.google.com/dr...
Thanks

Reply

Hi Gaetano S. !

Sorry for my VERY slow reply - it's been a crazy week :). And excellent screenshots - that's super helpful.

The thing that is very clear now (and wasn't before) is that the test.api_platform.client IS present. And... that's interesting - because that's exactly the service that ApiTestCase looks for (and fails if it's not there): https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php#L56

So... let's do some debugging! First, I'd like you to actually open the core ApiTestCase file - so literally, this file - https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php - it will live at vendor/api-platform/core/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php.

Right after this line - https://github.com/api-platform/core/blob/2c87089c4330cd2f571f023fbcc36b5edd2b4dbb/src/Bridge/Symfony/Bundle/Test/ApiTestCase.php#L50 - I'd like you to add:


dd($kernel->getEnvironment());

What does that print? All of your other screenshots indicate that the test.api_platform.client IS available. So then... why is it suddenly not available in your test? The only thing I can think of (at the moment) is that (for some reason) the kernel in the test is being booted in some other environment than "test". But... I kind of doubt that's the cause... just because it's a bit hard to misconfigure the tests in this way.

Basically... something is going very wrong - it might be some tiny misconfiguration that's giving really bad behavior. And actually, if you're able to (it might be quite big), get a screenshot of the entire kernel object:


dd($kernel);

Cheers!

Reply
TristanoMilano Avatar
TristanoMilano Avatar TristanoMilano | weaverryan | posted 3 months ago | edited

I know, that comment is two years old, but I am loosing my mind, because I am getting the same error

1) App\Tests\API\FooBarTest::testGetCollection
LogicException: You cannot create the client used in functional tests if the "framework.test" config is not set to true.

This is my framework.yaml

# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
    secret: '%env(APP_SECRET)%'
    #csrf_protection: true
    http_method_override: false

    # Enables session support. Note that the session will ONLY be started if you read or write from it.
    # Remove or comment this section to explicitly disable session support.
    session:
        handler_id: null
        cookie_secure: auto
        cookie_samesite: lax
        storage_factory_id: session.storage.factory.native

    #esi: true
    #fragments: true
    php_errors:
        log: true

when@test:
    framework:
        test: true
        session:
            storage_factory_id: session.storage.factory.mock_file

this is my composer.json

{
    "type": "project",
    "license": "proprietary",
    "minimum-stability": "stable",
    "prefer-stable": true,
    "require": 
      "php": ">=8.1",
      "ext-ctype": "*",
      "ext-curl": "*",
      "ext-iconv": "*",
      "actived/microsoft-teams-notifier": "^1.2",
      "api-platform/core": "^2.6",
      "aws/aws-sdk-php": "^3.232",
      "doctrine/annotations": "^1.13",
      "doctrine/doctrine-bundle": "^2.5",
      "doctrine/doctrine-migrations-bundle": "^3.2",
      "doctrine/orm": "^2.11",
      "dompdf/dompdf": "^2.0",
      "dunglas/doctrine-json-odm": "^1.2",
      "eluceo/ical": "^2.7",
      "firebase/php-jwt": "^6.2",
      "h4cc/wkhtmltopdf-amd64": "^0.12.4",
      "justinrainbow/json-schema": "^5.2",
      "knplabs/doctrine-behaviors": "^2.6",
      "knplabs/knp-menu-bundle": "^3.2",
      "knplabs/knp-time-bundle": "^1.19",
      "knpuniversity/oauth2-client-bundle": "^2.9",
      "league/flysystem-aws-s3-v3": "^3.2",
      "lexik/jwt-authentication-bundle": "^2.14",
      "liip/imagine-bundle": "^2.8",
      "nelmio/cors-bundle": "^2.2",
      "oneup/flysystem-bundle": "^4.4",
      "phpdocumentor/reflection-docblock": "^5.3",
      "phpstan/phpdoc-parser": "^1.2",
      "ramsey/uuid": "^4.2",
      "ramsey/uuid-doctrine": "^1.8",
      "sensio/framework-extra-bundle": "^6.2",
      "symfony/asset": "6.2.*",
      "symfony/console": "6.2.*",
      "symfony/doctrine-messenger": "6.2.*",
      "symfony/dotenv": "6.2.*",
      "symfony/expression-language": "6.2.*",
      "symfony/flex": "^2",
      "symfony/form": "6.2.*",
      "symfony/framework-bundle": "6.2.*",
      "symfony/http-client": "6.2.*",
      "symfony/messenger": "6.2.*",
      "symfony/monolog-bundle": "^3.8",
      "symfony/property-access": "6.2.*",
      "symfony/property-info": "6.2.*",
      "symfony/proxy-manager-bridge": "6.2.*",
      "symfony/runtime": "6.2.*",
      "symfony/security-bundle": "6.2.*",
      "symfony/serializer": "6.2.*",
      "symfony/translation": "6.2.*",
      "symfony/twig-bundle": "6.2.*",
      "symfony/uid": "6.2.*",
      "symfony/ux-autocomplete": "^2.6",
      "symfony/validator": "6.2.*",
      "symfony/webpack-encore-bundle": "^1.14",
      "symfony/yaml": "6.2.*",
      "twig/cssinliner-extra": "^3.4",
      "twig/extra-bundle": "^3.4",
      "vich/uploader-bundle": "^1.19"
    ,
    "config": {
      "allow-plugins": {
        "composer/package-versions-deprecated": true,
        "symfony/flex": true,
        "symfony/runtime": true,
        "phpstan/extension-installer": true
      },
      "optimize-autoloader": true,
      "preferred-install": {
        "*": "dist"
      },
      "sort-packages": true
    },
    "autoload": {
      "psr-4": {
        "App\\": "src/"
      }
    },
    "autoload-dev": {
      "psr-4": {
        "App\\Tests\\": "tests/"
      }
    },
    "replace": {
      "symfony/polyfill-ctype": "*",
      "symfony/polyfill-iconv": "*",
      "symfony/polyfill-php72": "*",
      "symfony/polyfill-php73": "*",
      "symfony/polyfill-php74": "*",
      "symfony/polyfill-php80": "*"
    },
    "scripts": {
      "auto-scripts": {
        "cache:clear": "symfony-cmd",
        "assets:install %PUBLIC_DIR%": "symfony-cmd"
      },
      "post-install-cmd": [
        "@auto-scripts"
      ],
      "post-update-cmd": [
        "@auto-scripts"
      ],
      "phpstan": "phpstan analyse -c phpstan.neon src tests --level 1 --no-progress",
      "tests": "phpunit",
      "ci": [
        "@phpstan",
        "@tests"
      ]
    },
    "conflict": {
      "symfony/symfony": "*"
    },
    "extra": {
      "symfony": {
        "allow-contrib": false,
        "require": "6.2.*"
      }
    },
    "require-dev": {
      "dama/doctrine-test-bundle": "^7.2",
      "phpstan/extension-installer": "^1.1",
      "phpstan/phpstan": "^1.4",
      "phpstan/phpstan-doctrine": "^1.2",
      "phpstan/phpstan-symfony": "^1.1",
      "phpunit/phpunit": "^9.5",
      "symfony/browser-kit": "6.2.*",
      "symfony/css-selector": "6.2.*",
      "symfony/maker-bundle": "^1.36",
      "symfony/phpunit-bridge": "^6.0",
      "symfony/stopwatch": "6.2.*",
      "symfony/web-profiler-bundle": "6.2.*",
      "vimeo/psalm": "^4.20"
    }
  }
  
{
    "type": "project",
    "license": "proprietary",
    "minimum-stability": "stable",
    "prefer-stable": true,
    "require": 
      "php": ">=8.1",
      "ext-ctype": "*",
      "ext-curl": "*",
      "ext-iconv": "*",
      "actived/microsoft-teams-notifier": "^1.2",
      "api-platform/core": "^2.6",
      "aws/aws-sdk-php": "^3.232",
      "doctrine/annotations": "^1.13",
      "doctrine/doctrine-bundle": "^2.5",
      "doctrine/doctrine-migrations-bundle": "^3.2",
      "doctrine/orm": "^2.11",
      "dompdf/dompdf": "^2.0",
      "dunglas/doctrine-json-odm": "^1.2",
      "eluceo/ical": "^2.7",
      "firebase/php-jwt": "^6.2",
      "h4cc/wkhtmltopdf-amd64": "^0.12.4",
      "justinrainbow/json-schema": "^5.2",
      "knplabs/doctrine-behaviors": "^2.6",
      "knplabs/knp-menu-bundle": "^3.2",
      "knplabs/knp-time-bundle": "^1.19",
      "knpuniversity/oauth2-client-bundle": "^2.9",
      "league/flysystem-aws-s3-v3": "^3.2",
      "lexik/jwt-authentication-bundle": "^2.14",
      "liip/imagine-bundle": "^2.8",
      "nelmio/cors-bundle": "^2.2",
      "oneup/flysystem-bundle": "^4.4",
      "phpdocumentor/reflection-docblock": "^5.3",
      "phpstan/phpdoc-parser": "^1.2",
      "ramsey/uuid": "^4.2",
      "ramsey/uuid-doctrine": "^1.8",
      "sensio/framework-extra-bundle": "^6.2",
      "symfony/asset": "6.2.*",
      "symfony/console": "6.2.*",
      "symfony/doctrine-messenger": "6.2.*",
      "symfony/dotenv": "6.2.*",
      "symfony/expression-language": "6.2.*",
      "symfony/flex": "^2",
      "symfony/form": "6.2.*",
      "symfony/framework-bundle": "6.2.*",
      "symfony/http-client": "6.2.*",
      "symfony/messenger": "6.2.*",
      "symfony/monolog-bundle": "^3.8",
      "symfony/property-access": "6.2.*",
      "symfony/property-info": "6.2.*",
      "symfony/proxy-manager-bridge": "6.2.*",
      "symfony/runtime": "6.2.*",
      "symfony/security-bundle": "6.2.*",
      "symfony/serializer": "6.2.*",
      "symfony/translation": "6.2.*",
      "symfony/twig-bundle": "6.2.*",
      "symfony/uid": "6.2.*",
      "symfony/ux-autocomplete": "^2.6",
      "symfony/validator": "6.2.*",
      "symfony/webpack-encore-bundle": "^1.14",
      "symfony/yaml": "6.2.*",
      "twig/cssinliner-extra": "^3.4",
      "twig/extra-bundle": "^3.4",
      "vich/uploader-bundle": "^1.19"
    ,
    "config": {
      "allow-plugins": {
        "composer/package-versions-deprecated": true,
        "symfony/flex": true,
        "symfony/runtime": true,
        "phpstan/extension-installer": true
      },
      "optimize-autoloader": true,
      "preferred-install": {
        "*": "dist"
      },
      "sort-packages": true
    },
    "autoload": {
      "psr-4": {
        "App\\": "src/"
      }
    },
    "autoload-dev": {
      "psr-4": {
        "App\\Tests\\": "tests/"
      }
    },
    "replace": {
      "symfony/polyfill-ctype": "*",
      "symfony/polyfill-iconv": "*",
      "symfony/polyfill-php72": "*",
      "symfony/polyfill-php73": "*",
      "symfony/polyfill-php74": "*",
      "symfony/polyfill-php80": "*"
    },
    "scripts": {
      "auto-scripts": {
        "cache:clear": "symfony-cmd",
        "assets:install %PUBLIC_DIR%": "symfony-cmd"
      },
      "post-install-cmd": [
        "@auto-scripts"
      ],
      "post-update-cmd": [
        "@auto-scripts"
      ],
      "phpstan": "phpstan analyse -c phpstan.neon src tests --level 1 --no-progress",
      "tests": "phpunit",
      "ci": [
        "@phpstan",
        "@tests"
      ]
    },
    "conflict": {
      "symfony/symfony": "*"
    },
    "extra": {
      "symfony": {
        "allow-contrib": false,
        "require": "6.2.*"
      }
    },
    "require-dev": {
      "dama/doctrine-test-bundle": "^7.2",
      "phpstan/extension-installer": "^1.1",
      "phpstan/phpstan": "^1.4",
      "phpstan/phpstan-doctrine": "^1.2",
      "phpstan/phpstan-symfony": "^1.1",
      "phpunit/phpunit": "^9.5",
      "symfony/browser-kit": "6.2.*",
      "symfony/css-selector": "6.2.*",
      "symfony/maker-bundle": "^1.36",
      "symfony/phpunit-bridge": "^6.0",
      "symfony/stopwatch": "6.2.*",
      "symfony/web-profiler-bundle": "6.2.*",
      "vimeo/psalm": "^4.20"
    }
  }
  

and this is my phpunit.xml.dist:

<?xml version="1.0" encoding="UTF-8"?>

<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         colors="true"
         bootstrap="tests/bootstrap.php"
>
    <php>
        <ini name="display_errors" value="1"/>
        <ini name="error_reporting" value="-1"/>
        <server name="APP_ENV" value="test" force="true"/>
        <server name="SHELL_VERBOSITY" value="-1"/>
        <server name="SYMFONY_PHPUNIT_REMOVE" value=""/>
        <server name="SYMFONY_PHPUNIT_VERSION" value="9.5"/>
    </php>

    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">src</directory>
        </include>
    </coverage>

    <listeners>
        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener"/>
    </listeners>

    <extensions>
        <extension class="DAMA\DoctrineTestBundle\PHPUnit\PHPUnitExtension"/>
    </extensions>

    <!-- Run `composer require symfony/panther` before enabling this extension -->
    <!--
    <extensions>
        <extension class="Symfony\Component\Panther\ServerExtension" />
    </extensions>
    -->
</phpunit>

My Unit-tests work just fine, but as soon I want to use the ApiTestCase withe the client I get the error from above, here is the TestCase which is enough to get the error:

<?php

namespace App\Tests\API;


use ApiPlatform\Core\Bridge\Symfony\Bundle\Test\ApiTestCase;

class FooBarTest extends ApiTestCase
{
    public function testGetCollection(): void
    {
        $environment = static::createClient()->getKernel()->getEnvironment();
        var_dump($environment); // dump the current environment to the console

    }

}

I also tried that commands you reccomended for debugging:

# does this work and show you the service?
php bin/console debug:container --env=test test.client

Information for Service "test.client"
=====================================

 Simulates a browser and makes requests to a Kernel object.

 ---------------- ---------------------------------------------- 
  Option           Value                                         
 ---------------- ---------------------------------------------- 
  Service ID       test.client                                   
  Class            Symfony\Bundle\FrameworkBundle\KernelBrowser  
  Tags             -                                             
  Public           yes                                           
  Synthetic        no                                            
  Lazy             no                                            
  Shared           no                                            
  Abstract         no                                            
  Autowired        no                                            
  Autoconfigured   no                                            
  Usages           test.api_platform.client                      
 ---------------- ---------------------------------------------

# I'm guessing this will fail. And if so, that's the mystery: why isn't this service added?
php bin/console debug:container --env=test test.api_platform.client

Information for Service "test.api_platform.client"
==================================================

 Convenient test client that makes requests to a Kernel object.

 ---------------- ---------------------------------------- 
  Option           Value                                   
 ---------------- ---------------------------------------- 
  Service ID       test.api_platform.client                
  Class            ApiPlatform\Symfony\Bundle\Test\Client  
  Tags             -                                       
  Public           yes                                     
  Synthetic        no                                      
  Lazy             no                                      
  Shared           no                                      
  Abstract         no                                      
  Autowired        no                                      
  Autoconfigured   no                                      
  Usages           none                                    
 ---------------- ---------------------------------------- 



# look for "test.client.parameters" - do you see it? It will probably be set to an empty array, but it SHOULD be present
php bin/console debug:container --env=test --parameters
...
 test.client.parameters                                             []  
 ...

Finally I also tried the dumps directy from the ApiTestCase.php

dd($kernel->getEnvironment());

Testing 
^ "dev"

So that seems to be like intended, But the following check in the ApiTestCase.php throws an error:

try {
            /**
             * @var Client
             */
            $client = $kernel->getContainer()->get('test.api_platform.client');
        } catch (ServiceNotFoundException $e) {
            if (!class_exists(AbstractBrowser::class) || !trait_exists(HttpClientTrait::class)) {
                throw new \LogicException('You cannot create the client used in functional tests if the BrowserKit and HttpClient components are not available. Try running "composer require --dev symfony/browser-kit symfony/http-client".');
            }

            throw new \LogicException('You cannot create the client used in functional tests if the "framework.test" config is not set to true.');
        }

And that is because 'test.api_platform.client' ist not in the kernel contriner. If I make the following dump from within the ApitestCase.php:

dd($kernel->getContainer());

I will get (outsourced that dump from the comment, because it is 1500 lines) :

https://transfer.acted.org/download/77d3156546b2f151/#hjAAKsnSaQ0kBd_xImPKrg

Reply

Hey @TristanoMilano!

Oh man, this sounds rough! Immediately, one thing stuck out to me:

dd($kernel->getEnvironment());

Testing 
^ "dev"

That actually does NOT look correct to me. We need the environment to be test , else we are booting a dev container. The 1500 line container dump seems to confirm my suspicion: there is no test.client service and it shows that the environment is dev. So, for some reason, your kernel is getting booted in the wrong environment!

Unless there are some additional layers in your situation, the kernel is created here - https://github.com/symfony/symfony/blob/584c21016582108e993857cfc0919adc61b468b8/src/Symfony/Bundle/FrameworkBundle/Test/KernelTestCase.php#L111-L136 - so you should be able to add some debug statements to figure out why it thinks the environment should be dev. I've seen people have, without realizing it, some old APP_ENV=dev real environment variable set on their system, for example.

I hope this helps!

Reply
TristanoMilano Avatar
TristanoMilano Avatar TristanoMilano | weaverryan | posted 3 months ago | edited

Thanks for the reply. I noticed something interesting, when I do the following dump:

dd($_ENV['APP_ENV'], $_SERVER['APP_ENV']);

from inside the createKernel fcuntion of the KernelTestCase class, I get the following result:

^ "dev"
^ "test"

I do not know why it is set like that, I can not find anything in my code or configs that sets it like that. Every similar Symfony project does not have that behavior. It's a big mistery to me. My hacky/dirty workaround for now is to add this to my TestClass:

protected function setUp(): void
    {
        $_ENV['APP_ENV'] = 'test';
    }

But that really gives me headaches :)

Reply

Hey @TristanoMilano

That's a bit odd indeed, your workaround may work but that's not the way to change env vars in Symfony. To change an env var for the test environment, you can create a .env.test.local file and override the APP_ENV variable

APP_ENV=test

In theory, you should not need to do this because when you boot the kernel in a test case, Symfony uses the test environment by default

I hope it helps. Cheers!

Reply
TristanoMilano Avatar
TristanoMilano Avatar TristanoMilano | MolloKhan | posted 3 months ago | edited

Thank you for your reply. Using .env.test.local and setting

APP_ENV=test

in there was what I tried before. But that still lead to that error.

Because in that case the following dump from inside KernelTestCase

$appEnv = getenv('APP_ENV');
dd($_ENV['APP_ENV'], $appEnv, $_SERVER['APP_ENV']);

still returns:

^ "dev"
^ "dev"
^ "test"

That is all very, very strange.

Reply

What do you get if you dump the environment from the $kernel object (after instantiating it)

self::bootKernel();
dump(self::$kernel->getEnvironment());

I'm assuming you're inside a PHPUnit WebTestCase

Reply
TristanoMilano Avatar
TristanoMilano Avatar TristanoMilano | MolloKhan | posted 3 months ago

When I do this:

self::bootKernel();
dump(self::$kernel->getEnvironment());

from inside a TestCase that extends ApiTestCase I get:

^ "dev"

So for now I have to go with my dirty workaround. I do not get why. Have a dozen of similar apps running that do not behave like that.

I can't get that one debugged :(

Reply

Hey TristanoMilano,

Check your .env files, probably you override Symfony environment there somehow? Also, check for phpunit.xml files you have in the project - probably there might be also overwritten Symfony env?

Also, during running those tests could to turn off Symfony web server? i.e. to make it does NOT server your 127.0.0.1 host. I wonder if tests will still work and show dev env - probably in your tests you send a really API request to the 127.0.0.1 ? And if that address serves your website in dev env - that makes sense you see "dev" env in your tests :)

I hope this helps!

Cheers!

Reply
Gaetano S. Avatar

Hi Ryan,
don't worry about your reply. You have a lot of things to do, other users needs your helps. If I have to wait, I do. I updated the folder of screenshots. I understand a little bit more the problem. I mean, I know how to change the env. Basically, when I run the docker-compose, the service read the env.local with APP_ENV=..... If I put there 'test', I can running phpunit test with the env->test. Or better, I put 'dev' and I run unit tests in this way -> APP_ENV=test php bin/phpunit. And now test passed. Ok, I have an error:

-> [error] Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\HttpException: "Full authentication is required to access this resource." at /var/www/html/vendor/symfony/security-http/Firewall/ExceptionListener.php line 198

but I think it is normal because I need to create a user, auth and so on. I have to continue with the tutorial. I don't know how to thank you. I owe you a dinner. If you pass in the south-est of France (Cote d'Azur), you are welcome :). See you at my next error and thanks again.

Reply

Hey Gaetano S.!

> don't worry about your reply. You have a lot of things to do, other users needs your helps

I *do* really appreciate this :). We try to help in the comments because we like helping... but if we're going to do it, we also like to be timely ;).

> Or better, I put 'dev' and I run unit tests in this way -> APP_ENV=test php bin/phpunit. And now test passed

Yep, this makes (mostly) a lot of sense: your app will run in "dev" mode normally, but you are overriding it when you run the tests. The Docker setup was actually the missing piece for me to understand this thing fully! So normally, if you do nothing, your tests will automatically execute in the "test" environment - you can see that here - https://github.com/symfony/...

But, if you set a real APP_ENV environment variable (which Docker will do if you point it at the .env.local file), then the test will use THAT instead. THAT is why you had the problem: your app was using the env var set by Docker instead of defaulting to test. Oh, the complexities of modern setup and env vars :).

> -> [error] Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\HttpException: "Full authentication is required to access this resource." at /var/www/html/vendor/symfony/security-http/Firewall/ExceptionListener.php line 198

So this means basically what it says: you're trying to access an endpoint that requires authentication, but you are not logged in. Do you get this when you run the tests? Or when you use the API in a browser? If in the browser, you can go to the homepage and log in there.

> I owe you a dinner. If you pass in the south-est of France (Cote d'Azur)

Well that sounds lovely! I can't wait until the world has the normalcy to visit France again (have never been to the South of France!).

Cheers!

Reply
Gaetano S. Avatar

Hi,
I did get the error on running tests. When I have time, I will go on. You know..my job, my children :).

Cheers!

Reply
Gaetano S. Avatar
Gaetano S. Avatar Gaetano S. | Victor | posted 3 years ago

Ah! You are right....I'm sorry, my fault...and I'm sorry, Diego. I noticed that I mess up a little bit all folders about tests. I need to restart and try to fix everything. Thanks again. I let you know if I will do dumb stuff :).

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This tutorial works great for Symfony 5 and API Platform 2.5/2.6.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.1.3, <8.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^2.1", // v2.4.5
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/annotations": "^1.0", // 1.13.2
        "doctrine/doctrine-bundle": "^1.6", // 1.11.2
        "doctrine/doctrine-migrations-bundle": "^2.0", // v2.0.0
        "doctrine/orm": "^2.4.5", // v2.7.2
        "nelmio/cors-bundle": "^1.5", // 1.5.6
        "nesbot/carbon": "^2.17", // 2.21.3
        "phpdocumentor/reflection-docblock": "^3.0 || ^4.0", // 4.3.1
        "symfony/asset": "4.3.*", // v4.3.2
        "symfony/console": "4.3.*", // v4.3.2
        "symfony/dotenv": "4.3.*", // v4.3.2
        "symfony/expression-language": "4.3.*", // v4.3.2
        "symfony/flex": "^1.1", // v1.18.7
        "symfony/framework-bundle": "4.3.*", // v4.3.2
        "symfony/http-client": "4.3.*", // v4.3.3
        "symfony/monolog-bundle": "^3.4", // v3.4.0
        "symfony/security-bundle": "4.3.*", // v4.3.2
        "symfony/twig-bundle": "4.3.*", // v4.3.2
        "symfony/validator": "4.3.*", // v4.3.2
        "symfony/webpack-encore-bundle": "^1.6", // v1.6.2
        "symfony/yaml": "4.3.*" // v4.3.2
    },
    "require-dev": {
        "hautelook/alice-bundle": "^2.5", // 2.7.3
        "symfony/browser-kit": "4.3.*", // v4.3.3
        "symfony/css-selector": "4.3.*", // v4.3.3
        "symfony/maker-bundle": "^1.11", // v1.12.0
        "symfony/phpunit-bridge": "^4.3", // v4.3.3
        "symfony/stopwatch": "4.3.*", // v4.3.2
        "symfony/web-profiler-bundle": "4.3.*" // v4.3.2
    }
}
userVoice