If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeTime 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.
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
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.
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
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.
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 ?? '';
}
`
...
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 ?? '';
}
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!
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">https://gist.github.com/rakodev/2b80a2efbdddea8895e1bcb1a7f524e1</a>
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!
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.
NP man!
Yeah, probably they didn't realize the impact of the change or they didn't have any other alternative. Who knows...
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();
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
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
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?
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!
Oh wait. I get it now. I added this to .env.test and it fixed it:
DATABASE_TEST_URL=$DATABASE_URL
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!
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!
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.
Is it possible to tie into the router service to generate routes for the test cases?
$client->request('GET', ROUTE_SERVICE);
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!
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))
Ah, I understand now. You can use the routing service but you'd still type the *name* of the route, for example "api_users".
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
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 :(
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!
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
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/*
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
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!
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.
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!
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 :(.
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!
Hi,
Did you have the possibility to check my answer about the checks to do? Thanks for your help
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!
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
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!
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 :)
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!
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.
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
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 :(
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!
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.
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!
Hi,
I did get the error on running tests. When I have time, I will go on. You know..my job, my children :).
Cheers!
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 :).
// 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
}
}
In my case v2.5.3 all I had to do was:
`
public function testCreatePost()
{
}
`
Seems it was updated by api platform, authentication should be the first check before anything.