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 SubscribeTo test our API, we're going to use PHPUnit and some other tools to make a request into our API, get back a response, and assert different things about it, like the status code, that the response is JSON and that the JSON has the right keys.
API platform recently introduced some really nice tools for doing all this. In fact it was so recent that... they're not released yet! When I run:
composer show api-platform/core
...you can see that I'm using Api Platform 2.4.5. These new features will come out in version 2.5. So if you're already on version 2.5, you can skip to the end of this video where we bootstrap our first test.
But if you're with me on 2.4.5... welcome! We're going to do some hacking and backport those features manually into our app. If you downloaded the course code, you should have a tutorial/
directory inside. If you don't have this, try downloading the code again to get it.
See this Test/
directory? This contains a copy of all of those new testing classes. The only difference is that I've changed their namespaces to use App\ApiPlatform\Test
instead of what they look like inside of ApiPlatform: ApiPlatform\Core\Bridge\Symfony\Bundle\Test
.
That's because we're going to move all of this directly into our src/
directory. Start by creating a new ApiPlatform/
directory... and then copy Test/
and paste it there.
I'm also going to right click on the tutorial/
directory and say "Mark as Excluded". That tells PhpStorm to ignore those files... which is important so it doesn't get confused by seeing the same classes in two places.
Beyond the new classes, we need to make one other change. Open up config/
and create a new file: services_test.yaml
. Thanks to its name, this file will only be loaded in the test
environment. Inside, add services:
and create a new service called test.api_platform.client:
with class:
App\ApiPlatform\Test\Client and arguments: ['@test.client']
. Also add public: true
.
services: | |
# added so we can use the new API Platform test tools before | |
# they are released. In API Platform 2.5, this won't be needed. | |
test.api_platform.client: | |
class: App\ApiPlatform\Test\Client | |
arguments: ['@test.client'] | |
public: true |
These two steps completely replicate what ApiPlatform will give you in version 2.5. Well... unless they change something. I don't normally show unreleased features... because they might change... but these tools are so useful, I just had to include them. When 2.5 does come out, there could be a few differences.
Ok, let's create our first test! Inside the tests/
directory, create a functional/
directory and then a new class: CheeseListingResourceTest
.
This isn't an official convention, but because we're functionally testing our API... and because everything in API Platform is based on the API resource, it makes sense to create a test class for each resource: we test the CheeseListing
resource via CheeseListingResourceTest
.
Make this extend ApiTestCase
- that's one of the new classes we just moved into our app. If you're using API Platform 2.5, the namespace will be totally different - it'll start with ApiPlatform\Core
.
... lines 1 - 2 | |
namespace App\Tests\Functional; | |
use App\ApiPlatform\Test\ApiTestCase; | |
class CheeseListingResourceTest extends ApiTestCase | |
... lines 8 - 14 |
The first thing I want to test is the POST operation - the operation that creates a new CheeseListing
. A few minutes ago, under collectionOperations
, we added an access control that made it so that you must be logged into use this. Oh... and I duplicated the collectionOperations
key too! The second one is overriding the first... so let's remove the extra one.
Anyways, for our first test, I want to make sure this security is working. Add public function testCreateCheeseListing()
. And inside, make sure this all isn't an elaborate dream with $this->assertEquals(42, 42)
.
... lines 1 - 8 | |
public function testCreateCheeseListing() | |
{ | |
$this->assertEquals(42, 42); | |
} | |
... lines 13 - 14 |
Ok! Run that with:
php bin/phpunit
We're alive! Next, let's turn this into a true functional test against our API! We'll also take care of some other details to make our tests shine.
Running into an issue with Symfony 5.2.3, api-platform 2.6 and SYMFONY_PHPUNIT_VERSION = 8.5"
I followed this tutorial, but it seems that Symfony ignore the services_test.yaml.
The service “test.api_platform.client” has the class ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client instead of App\ApiPlatform\Test\Client.
php bin/console debug:container test.api_platform.client --env=test
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\Core\Bridge\Symfony\Bundle\Test\Client
Tags -
Public yes
Synthetic no
Lazy no
Shared no
Abstract no
Autowired no
Autoconfigured no
---------------- ----------------------------------------------------
So get this Error back
PHPUnit 8.5.14 by Sebastian Bergmann and contributors.
Testing Project Test Suite
E 1 / 1 (100%)
Time: 4.76 seconds, Memory: 18.00 MB
There was 1 error:
1) App\Tests\Functional\CheesesListingResourceTest::testCreateCheeseListing
TypeError: Argument 1 passed to App\ApiPlatform\Test\ApiTestCase::getHttpClient() must be an instance of App\ApiPlatform\Test\Client or null, instance of ApiPlatform\Core\Bridge\Symfony\Bundle\Test\Client given,
How can I override the registration of that service?
Hey Manuel,
You can double-check that Symfony is loading your test services by adding a service definition there and then debug the container passing it the flag --env=test
If you want to override an existent service definition you only have to add a new definition in your services.yaml
file (in your case services_test.yaml) and set its ID exactly to the one you want to override (test.api_platform.client)
I hope it helps. Cheers!
Running into an issue with api-platform 2.5 and phpunit.
composer require test --dev
Updated phpunit.xml.dist to SYMFONY_PHPUNIT_VERSION=8.2
and created a file in tests/Functional/MyResrouceTest.php
Ran "php bin/phpunit" to download the files and it worked. After that I ran "php bin/phpunit" but doesn't actually try to run any tests. Instead it just outputs the the "help text" ie: "php bin/phpunit --help"
Any ideas?
Hey David B.
Does your test class extends from PHPUnit's base test class?
Can I see your phpunit.xml.dist file? It should look like this https://github.com/symfony/...
Cheers!
Looks like i'm running into the same issue as before with a new project. 'php bin/phpunit' can't find the config 'phpunit.xml.dist' The only way i can get it to work is by running php bin/phpunit --configuration '/var/www/html/phpunit.xml.dist'. Any idea's as to why this is happening? ( I can't even figure out why its working in my original project. Repeated the same steps as before and it still doesn't work.
Hmm, that's interesting. What PHPUnit and Symfony version are you using? Did you install the test-pack library? https://packagist.org/packa...
Cheers!
Symfony 5.1. Since the .xml.dist gets ignored It tries to download 8.3.5. I suspect this might be a symfony bridge issue and not really a phpunit issue. When I pass the config param it works fine.
Hey David B.
Yeah, I think so too but I couldn't find any issues on the PHPUnitBridge component repository so perhaps there's something else in your environment causing that behavior
Hi Diego,
./tests/Functional/AllergyResourceTest.php : https://pastebin.com/2Mi6xfqX
./phpunit.xml.dist: https://pastebin.com/Epg1kKP3
To test I run:php bin/phpunit
and it just outputs the phpunit help screen.
Update:
I decided to start over and re-run composer require test --dev and updated SYMFONY_PHPUNIT_VERSION to 9 instead of 8.2 and its working now.
Hey David B.
Sorry for my late response, life got me busy :p
Hmm, so downgrading PHPUnit was the answer? Unless something got installed incorrectly by some reason and you only had to re-install your vendor's
I upgraded from 8 to 9. Perhaps something could have installed incorrectly as I did delete my vendors and reinstall using composer install.
While its working, something doesn't really seem right. Runningdocker-compose exec php php bin/phpunit
takes a very long time to run. OK (20 tests, 102 assertions)<br />
Takes Time: 01:58.450, Memory: 52.50 MB
Does that seem correct to you?
Here is an example of one of my test resources https://pastebin.com/8m3mN15c
That's slow to me but that also depends on your local machine.
Are you using Symfony's web server?
Which PHP version are you using?
Are you on Windows, Mac, or Ubuntu?
Are you using a proxy or something that may be slowing your HTTP requests?
It slowed down after upgrading from 8 to 9 or it's that slow in any version?
Sorry, too many questions :D
I'm running docker with php7.4-fpm. My host is Ubuntu 20.04. The docker guest is also ubuntu. I haven't tested any other version other than 9. Not running a proxy but I am running php and apache on different servers locally via:
`
<FilesMatch ".php$"> // EDIT:: for some reason the code block is adding [=""]
SetHandler proxy:fcgi://[IPADDRESS]:9000
</FilesMatch>
`
Hey David B.
You have pretty complex setup, so there is a lot of places which can make tests slower. The best way to check it is to try run tests one by one and see if all tests are slow or only some of them and depending on it try to debug what is going wrong slow.
Cheers!
Wanting to write my first functional test in Symfony 4.4 app (not APIP), I followed the "Testing" page in Symfony Docs. It focuses around components included in `test-pack`, that apparently were built for crawling/scrapping. My app has only API endpoints, so it's reduntant. Also, I found `BrowserKit`'s client confusing to use.
Can I somehow write functional tests using `HttpClient` component, while keeping the benefits of "fake http client" (doing requests internally, in PHP only)? To my surprise, following steps from this chapter do work in normal Symfony project. Nonetheless, it's still several files literally taken from vendor, with some methods that won't work. So, I wonder if there's a cleaner way to achieve this - any unbuild or another component? Also the `assertJsonContains` `assertJsonEquals` methods seem nice.
Hey SirRFI
I believe the HttpClient component is your best shot for testing api endpoints in Symfony. Also, all the vendor files copied was just to workaround a problem with ApiPlatform 2.4 version, if you use 2.5+ version then you shouldn't need to copy any files
Cheers!
Thanks for reply MolloKhan, but this is not what I asked. Sorry if my message was unclear, let me try fix that:
I am aware this course is about ApiPlatform and that the code was backported from then unreleased 2.5 to 2.4, which is no longer required to do when using ApiPlatform 2.5 or newer. The project I mentioned does not use ApiPlatform, but regular Symfony 4.4.
Functional testing in Symfony, as described in the documentation, uses client from BrowserKit component. It is said to be attached to the framework's kernel, so no actual requests are made during the tests. To my understanding, it causes everything to happen internally in PHP, without the need to having actual server running and extra configuration. As result, it's easier to use and significantly more performant, which is cool.
However, I spent few hours working with the tests as described in the docs, and found the client to be confusing/unclear to use. Given my previous positive experience with HttpClient component and examples in following chapters of this course, I wondered if it's possible to use it in tests instead of BrowserKit's.
The backported code in this chapter allows to use HttpClient component, while keeping the benefits described above. According to what I was told just recently, it still uses BrowserKit underneath, as if only interface changed to HttpClient's. It also brings ApiPlatform exclusive methods (related to schemas for example) and some extra assertions.
This sounds like something I am looking for: the same good tests, but with better interface. So, I gave it a try and ported the files to my project - and they do work. Well, I had to remove some parts that wouldn't work, like the schema parts.
While it does work, it's still some kind of workaround as well, so I wonder if there's cleaner way to do it in Symfony project. Some inbuilt way, or a component? The extra assertions, like JsonContains are also convenient.
Please correct me if I am wrong somewhere.
Why even bother You may ask? Aside from HttpClient's interface being easier and more readable to me, the other components from test-pack seems to be focused around crawling/scrapping, which I simply don't need for testing API endpoints.
Hey SirRFI, sorry for the late response
I found this in the ApiPlatform docs: https://api-platform.com/docs/core/testing/
but it doesn't say anything of how to port the testing utilities to your application. So, I believe it won't be that easy as installing a library via composer. What you can do is basically what you did, copy-paste the ApiTestCase
class from the ApiPlatform project and make your tests extend from it
You may want to ask for this to the ApiPlatform team, I think it's possible for them to offer the testing utilities as a separate component
Cheers!
// 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
}
}
If you are using the downloaded code as I am, you can update only the api-platform/core package to 2.5.0 (compser update api-platform/core). And if you are using PHPStorm (You should), exlude the "tutorial" directory. IDE will include the proper use statement when extending from ApiTestCase.