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

Mad Test Debugging

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 $10.00

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

Login Subscribe

When we mess up in a web app, we see Symfony's giant exception page. I want that same experience when I'm building an API.

At the root of the project there's a resources/ directory with an ApiTestCase.php file. This has all the same stuff as our ApiTestCase plus some pretty sweet new debugging stuff.

Copy this and paste it over our class.

First, check out onNotSuccessfulTest():

... lines 1 - 64
protected function onNotSuccessfulTest(Exception $e)
{
if (self::$history && $lastResponse = self::$history->getLastResponse()) {
$this->printDebug('');
$this->printDebug('<error>Failure!</error> when making the following request:');
$this->printLastRequestUrl();
$this->printDebug('');
$this->debugResponse($lastResponse);
}
throw $e;
}
... lines 78 - 205

If you have a method with this name, PHPUnit calls it whenever a test fails. I'm using it to print out the last response so we can see what just happened.

I also added a few other nice things, like printLastRequestUrl().

... lines 1 - 90
protected function printLastRequestUrl()
{
$lastRequest = self::$history->getLastRequest();
if ($lastRequest) {
$this->printDebug(sprintf('<comment>%s</comment>: <info>%s</info>', $lastRequest->getMethod(), $lastRequest->getUrl()));
} else {
$this->printDebug('No request was made.');
}
}
... lines 101 - 200

Next up is debugResponse() use it if you want to see what a Response looks like:

... lines 1 - 101
protected function debugResponse(ResponseInterface $response)
{
$this->printDebug(AbstractMessage::getStartLineAndHeaders($response));
$body = (string) $response->getBody();
... lines 106 - 172
}
... lines 174 - 200

This crazy function is something I wrote - it knows what Symfony's error page looks like and tries to extract the important parts... so you don't have to stare at a giant HTML page in your terminal. I hate that. It's probably not perfect - and if you find an improvement and want to share it, you'll be my best friend.

And finally, whenever this class prints something, it's calling printDebug(). And right now, it's about as dull as you can get:

... lines 1 - 179
protected function printDebug($string)
{
echo $string."\n";
}
... lines 184 - 200

I think we can make that way cooler. But first, with this in place, it should print out the last response so we can see the error:

php bin/phpunit -c app src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php

Ah hah!

Catchable Fatal Error: Argument 1 passed to Programmer::setUser() must
be an instance of AppBundle\Entity\User, null given in ProgrammerController.php
on line 29.

So the problem is that when we delete our database, we're also deleting our hacked-in weaverryan user:

... lines 1 - 17
public function newAction(Request $request)
{
... lines 20 - 23
$programmer->setUser($this->findUserByUsername('weaverryan'));
... lines 25 - 30
}
... lines 32 - 33

Let's deal with that in a second - and do something cool first. So, remember how some of the app/console commands have really pretty colored text when they print? Well, we're not inside a console command in PHPUnit, but I'd love to be able to print out with colors.

Good news! It turns out, this is really easy. The class that handles the styling is called ConsoleOutput, and you can use it directly from anywhere.

Start by adding a private $output property that we'll use to avoid creating a bunch of these objects. Then down in printDebug(), say if ($this->output === null) then $this->output = new ConsoleOutput();. This is the $output variable you're passed in a normal Symfony command. This means we can say $this->output->writeln() and pass it the $string:

... lines 1 - 12
use Symfony\Component\Console\Output\ConsoleOutput;
... lines 14 - 15
class ApiTestCase extends KernelTestCase
{
... lines 18 - 29
/**
* @var ConsoleOutput
*/
private $output;
... lines 34 - 184
protected function printDebug($string)
{
if ($this->output === null) {
$this->output = new ConsoleOutput();
}
$this->output->writeln($string);
}
... lines 193 - 207
}

I'm coloring some things already, so let's see this beautiful art! Re-run the test:

php bin/phpunit -c app src/AppBundle/Tests/Controller/Api/ProgrammerControllerTest.php

Hey! That error is hard to miss!

Seeing the Exception Stacktrace!

Ok, one more debugging trick. What if we really need to see the full stacktrace? The response headers are printed on top - and one of those actually holds the profiler URL for this request. And to be even nicer, my debug code is printing that at the bottom too.

Pop that into the browser. This is the profiler for that API request. It has cool stuff like the database queries, but most importantly, there's an Exception tab - you can see the full, beautiful exception with stacktrace. This is huge.

Leave a comment!

34
Login or Register to join the conversation
Default user avatar

Hi there!! So.. when i use guzzlehttp/guzzle v6.* the code of resource/ApiTesterCase(from the start directory) makes phpStorm angry :P. i read some documentation as of the upgrades of guzzlehttp v5 and v6 but instead of fixing/changing the code i just downgraded the package to v 5. Should i bother fixing the changes to the current version for now? I'm not sure if this is a real or rehtorical question..

thanks in advance.

1 Reply

Hey emm!

Haha, for the purposes of learning the REST stuff, I would probably not bother upgrading. However, we actually *do* upgrade to Symfony 3 and Guzzle 6 between episode 3 and 4... so episode 4 has all the latest stuff (if you download its course code): http://knpuniversity.com/sc.... So, if you want, that should make upgrading your ApiTestCase to Guzzle 6 pretty easy :).

Cheers!

1 Reply
Shaun T. Avatar

I downloaded the ApiTestCase from episode 4 as I am using Guzzle veriosn 6, however when I run PHPUnit test I get the following error:

Fatal error: Uncaught Declaration of AppBundle\Test\ApiTestCase::onNotSuccessfulTest(Exception $e) should be compatible with PHPUnit\Framework\TestCase::onNotSuccessfulTest(Throwable $t)

And I am also getting the angry errors from PHPStorm regarding the Guzzle paths ;)

use GuzzleHttp\Message\AbstractMessage;
use GuzzleHttp\Message\ResponseInterface;
use GuzzleHttp\Subscriber\History;

Reply

Hey Shaun,

The message is clear enough - method's signature was changed, so you need to change "onNotSuccessfulTest(Exception $e)" to "onNotSuccessfulTest(Throwable $t)" as in parent class.

About the second problem, try to sync vendor/ directory, i.e. right click on vendor/ dir and select "Synchronize 'vendor'". If it does not help, try to re-execute "composer install" or remove vendor/ dir manually and re-run "composer install" again.

Cheers!

Reply
Shaun T. Avatar
Shaun T. Avatar Shaun T. | Victor | posted 5 years ago | edited

Thanks victor

The issue I am now having is that because I am using Symfony 4, the route for the API is not being picked up and I am getting following error:

HTML Summary (h1 and h2):
Symfony Exception
ResourceNotFoundException NotFoundHttpException
HTTP 404 Not Found
No route found for "POST /app_test.php/api/programmers"

Reply

Hey Shaun,

First of all, try to clear the cache. Also, make sure you use the correct namespace of the Route, it should be:

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

And try to debug a bit with "bin/console debug:router", do you see this route in the list?

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 4 years ago

I tried to modify debugResponse method like so:
protected function debugResponse(ResponseInterface $response){
foreach ($response->getHeaders() as $name => $values) {
$this->printDebug(sprintf('%s: %s', $name, implode(', ', $values)));
}
$body = (string) $response->getBody();

$contentType = $response->getHeader('Content-Type');
$contentType = $contentType[0];
if ($contentType == 'application/json' || strpos($contentType, '+json') !== false) {
$data = json_decode($body);
if ($data === null) {
// invalid JSON!
$this->printDebug($body);
} else {
// valid JSON, print it pretty
$this->printDebug(json_encode($data, JSON_PRETTY_PRINT));
}
} else {
// the response is HTML - see if we should print all of it or some of it
$isValidHtml = strpos($body, '</body>') !== false;

if ($isValidHtml) {
$this->printDebug('');
$crawler = new Crawler($body);

// very specific to Symfony's error page
$isError = $crawler->filter('.trace-line ')->count() > 0
|| strpos($body, 'Symfony Exception') !== false;
if ($isError) {
$this->printDebug('There was an Error!!!!');
$this->printDebug('');
} else {
$this->printDebug('HTML Summary (h1 and h2):');
}

foreach ($crawler->filter('.exception-message-wrapper h1')->extract(array('_text')) as $text) {
$text = $this->removeLineBreaks($text);
if ($isError) {
$this->printErrorBlock($text);
} else {
$this->printDebug($text);
}
}
foreach ($crawler
->filter('.trace-line')
->first()
->extract(array('_text')) as $text
){
$text = $this->removeLineBreaks($text);
if ($isError) {
$this->printErrorBlock($text);
} else {
$this->printDebug($text);
}
}

/*
* When using the test environment, the profiler is not active
* for performance. To help debug, turn it on temporarily in
* the config_test.yml file (framework.profiler.collect)
*/
$profilerUrl = $response->getHeader('X-Debug-Token-Link');
if ($profilerUrl) {
$fullProfilerUrl = $response->getHeader('Host')[0].$profilerUrl[0];
$this->printDebug('');
$this->printDebug(sprintf(
'Profiler URL: <comment>%s</comment>',
$fullProfilerUrl
));
}

// an extra line for spacing
$this->printDebug('');
} else {
$this->printDebug($body);
}
}
}

and onNotSuccessfulTest like so: protected function onNotSuccessfulTest(Throwable $t){
if ($lastResponse = $this->getLastResponse()) {
$this->printDebug('');
$this->printDebug('<error>Failure!</error> when making the following request:');
$this->printLastRequestUrl();
$this->printDebug('');

$this->debugResponse($lastResponse);
}

throw $t;
}

And I created function
protected function removeLineBreaks($text){
// remove line breaks so the message looks nice
$text = str_replace("\n", ' ', trim($text));
// trim any excess whitespace "foo bar" => "foo bar"
$text = preg_replace('/(\s)+/', ' ', $text);

return $text;
}

Probably I misjudge something but i don't see the errors that Symfony Flex raise like 'Argument 1 passed to App\Entity\Programmer::setUser() must be an instance of App\Entity\User or null, string given, called in ...'
in console when I run: APP_ENV=test bin/phpunit src/Tests/Controller/API/ProgrammerControllerTest.php

The fragments of the page I want to see in console is not like the page that I see in web browser when in WebBrowser/ProgrammerController in new method instead of $programmer->setUser($this->getUser()); i
put $programmer->setUser('nn') and submit the form ?

Can I print in console ResponseInterface $response argument in debugResponse method? Is there some configuration I must made in Flex to make happen what is seen in this video?

Reply

This looks like a duplicate of this comment: https://symfonycasts.com/sc... - follow the thread there.

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 4 years ago | edited

Before I asked I had tried this:


        protected function onNotSuccessfulTest(Throwable $t){
		if ($lastResponse = $this->getLastResponse()) {
			$this->printDebug('');
			$this->printDebug('<error>Failure!</error> when making the following request:');
			$this->printLastRequestUrl();
			$this->printDebug('');

			$this->debugResponse($lastResponse);
		}


		throw $t;
	}

But the stack trace is nowhere to be seen

Reply

Hey Diaconescu,

Your questions difficult to track because you asked them in different comments and even in threads :)

This looks good to me at the first sight. Hm, are you sure it is not printed? Because sometimes it might be printed in colors that difficult to spot in your terminal. Also, are you sure you get to this onNotSuccessfulTest() method during the execution? And are you sure you get exactly into that if statement? Try to debug it first using "die" statement like:


        protected function onNotSuccessfulTest(Throwable $t){
                die('DEBUG onNotSuccessfulTest');
		if ($lastResponse = $this->getLastResponse()) {
                        die('DEBUG if');
			$this->printDebug('');
			$this->printDebug('<error>Failure!</error> when making the following request:');
			$this->printLastRequestUrl();
			$this->printDebug('');

			$this->debugResponse($lastResponse);
		}

		throw $t;
	}

If you get the first die, comment it out and check if you see the second. This way you can find the problem and probably add more debug statements to better understand why. Also, it would be cool if you your terminal have search. If not, you can copy/paste the whole terminal output somewhere in a text editor and search for "DEBUG" keyword there.

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | Victor | posted 4 years ago

I did some debuggings. OnNotSuccessful test is surely hit.
After I put debug statement in getLastResponse method:
var_dump(self::$history)die() as the first line of it and I see that this static variable is never populated because this line prints:
array(0) {
}
This is the reason why if statement never executes.
The only culprit I may think is phpunit settings, but I can't come up with the correct ones.
As may I see setUBeforeClass is responsible to populate history static variable.
So t looks like this:
public static function setUpBeforeClass(){
$handler = HandlerStack::create();

$handler->push(Middleware::history(self::$history));

self::$staticClient = new Client([
'base_uri' => 'http://localhost:8000',
'http_errors' => false,
'handler' => $handler
]);

self::bootKernel();
}
Configuration in phpunit.xml.dist is the standard one:

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLS..." xsi:nonamespaceschemalocation="http://schema.phpunit.de/6...." backupglobals="false" colors="true" bootstrap="config/bootstrap.php">
<php>
<ini name="error_reporting" value="-1"/>
<env name="APP_ENV" value="test"/>
<env name="SHELL_VERBOSITY" value="-1"/>
</php>

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

<filter>
<whitelist>
<directory>src</directory>
</whitelist>
</filter>

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

I use Symfony 4.2 right now so I created .env.local file where I put APP_ENV=test
And I filled in env.test DATABASE_URL=mysql://root:mysql@127.0.0.1:3306/RestApiSiteTest

Reply

Hey Diaconescu,

<blockquote>

I did some debuggings. OnNotSuccessfulTest() is surely hit
</blockquote>

That's great, it means it should work.

<blockquote>
After I put debug statement in getLastResponse() method:
var_dump(self::$history)die() as the first line of it and I see that this static variable is never populated because this line prints:
array(0) {
}
</blockquote>

Hm, it might depend on what test you're running. Because you added "die;" statement - it means you run only first failed test. You can try without "die;", probably further tests will populate self::$history property, but it also depends on do you send any requests or no in tests. If no - I think it makes sense that it's empty. Or try to use "--filter" option to execute exactly the test that sends requests.

Btw, what version of PHPUnit do you use?

Hm, btw, it's a little known fact, but your .env.local file does not read in test environment as far as I know, see config/bootstrap.php file for more context. It means, that you need to have another one for test env that's called .env.test.local. But if you only change APP_ENV=test in your .env.local - it should be not important as far as you have APP_ENV is set in your phpunit.xml.dist:


<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.5/phpunit.xsd"
         backupGlobals="false"
         colors="true"
         bootstrap="config/bootstrap.php"
>
    <php>
        <ini name="error_reporting" value="-1" />
        <env name="APP_ENV" value="test" />
        <env name="SYMFONY_DEPRECATIONS_HELPER" value="weak" />
    </php>

    <!-- other config here ... -->

</phpunit>

Also, double check you have colors="true" in your phpunit.xml.dist.

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | posted 4 years ago

I ry to rework this tutorial with Symfony 4, Flex, php7.2, phpunit 6.5. I tried yor code from episode 4 but phpunit raise the error "Declaration of App\Tests\ApiTestCase::onNotSuccessfulTest(Exception $e) should be compatible with PHPUnit\Framework\TestCase::onNotSuccessfulTest(Throwable $t)"
How must look ApiTestCase to get the Symfony exception page using Throwable instead of exceptions?

Reply

Hey Diaconescu,

It's an easy fix! As you can see from the error message, the onNotSuccessfulTest() method is not compatible with one from the newer version - that was a BC break from PHPUnit. So the fix: just change ours ApiTestCase::onNotSuccessfulTest(\Exception $e) signature to ApiTestCase::onNotSuccessfulTest(\Throwable $t), i.e. allow a Throwable object to be passed there. And that's it :)

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | Victor | posted 4 years ago | edited

I tried to modify debugResponse method like so:


protected function debugResponse(ResponseInterface $response){
foreach ($response->getHeaders() as $name => $values) {
    $this->printDebug(sprintf('%s: %s', $name, implode(', ', $values)));
}
$body = (string) $response->getBody();

$contentType = $response->getHeader('Content-Type');
$contentType = $contentType[0];
if ($contentType == 'application/json' || strpos($contentType, '+json') !== false) {
$data = json_decode($body);
if ($data === null) {
// invalid JSON!
$this->printDebug($body);
} else {
// valid JSON, print it pretty
$this->printDebug(json_encode($data, JSON_PRETTY_PRINT));
}
} else {
// the response is HTML - see if we should print all of it or some of it
$isValidHtml = strpos($body, '</body>') !== false;

if ($isValidHtml) {
$this->printDebug('');
$crawler = new Crawler($body);

// very specific to Symfony's error page
$isError = $crawler->filter('.trace-line ')->count() > 0
|| strpos($body, 'Symfony Exception') !== false;
if ($isError) {
$this->printDebug('There was an Error!!!!');
$this->printDebug('');
} else {
$this->printDebug('HTML Summary (h1 and h2):');
}

foreach ($crawler->filter('.exception-message-wrapper h1')->extract(array('_text')) as $text) {
$text = $this->removeLineBreaks($text);
if ($isError) {
$this->printErrorBlock($text);
} else {
$this->printDebug($text);
}
}
foreach ($crawler
->filter('.trace-line')
->first()
->extract(array('_text')) as $text
){
$text = $this->removeLineBreaks($text);
if ($isError) {
$this->printErrorBlock($text);
} else {
$this->printDebug($text);
}
}

/*
* When using the test environment, the profiler is not active
* for performance. To help debug, turn it on temporarily in
* the config_test.yml file (framework.profiler.collect)
*/
$profilerUrl = $response->getHeader('X-Debug-Token-Link');
if ($profilerUrl) {
$fullProfilerUrl = $response->getHeader('Host')[0].$profilerUrl[0];
$this->printDebug('');
$this->printDebug(sprintf(
'Profiler URL: <comment>%s</comment>',
$fullProfilerUrl
));
}

// an extra line for spacing
$this->printDebug('');
} else {
$this->printDebug($body);
}
}
}

and onNotSuccessfulTest like so:


protected function onNotSuccessfulTest(Throwable $t){
if ($lastResponse = $this->getLastResponse()) {
$this->printDebug('');
$this->printDebug('<error>Failure!</error> when making the following request:');
$this->printLastRequestUrl();
$this->printDebug('');

$this->debugResponse($lastResponse);
}

throw $t;
}

And I created function


protected function removeLineBreaks($text){
// remove line breaks so the message looks nice
$text = str_replace("\n", ' ', trim($text));
// trim any excess whitespace "foo bar" => "foo bar"
$text = preg_replace('/(\s)+/', ' ', $text);

return $text;
}

Probably I misjudge something but i don't see the errors that Symfony Flex raise like 'Argument 1 passed to App\Entity\Programmer::setUser() must be an instance of App\Entity\User or null, string given, called in ...'
in console when I run: APP_ENV=test bin/phpunit src/Tests/Controller/API/ProgrammerControllerTest.php

The fragments of the page I want to see in console is not like the page that I see in web browser when in WebBrowser/ProgrammerController in new method instead of $programmer->setUser($this->getUser()); i
put $programmer->setUser('nn') and submit the form ?

Can I print in console ResponseInterface $response argument in debugResponse method? Is there some configuration I must made in Flex to make happen what is seen in this video?

Reply

Hey Diaconescu,

Wait, you don't see the errors of Symfony Flex? Or you don't see errors of PHPUnit? Because Symfony Flex does not relate to the error you mentioned like "Argument 1 passed to App\Entity\Programmer::setUser() must be an instance of App\Entity\User or null, string given, called in...".

Could you tell me if the tests work and pass before you made your changes?

Cheers!

Reply
Diaconescu Avatar
Diaconescu Avatar Diaconescu | Victor | posted 4 years ago

Ok, I wasn't clear enough.
In your screencast after ProgrammerControllerTest extends ApiTestCase when some phpunit test fails in console appear the actual reason of this failure. In your example this was 'Catchable Fatal Error: Argument 1 passed to Programmer::setUser() must
be an instance of AppBundle\Entity\User, null given in ProgrammerController.php
on line 29.'
In my case if I run: 'APP_ENV=test bin/phpunit src/Tests/Controller/API/ProgrammerControllerTest.php' i only see that respective test fails:
1) App\Tests\Controller\API\ProgrammerControllerTest::testPOST
Failed asserting that 404 matches expected 201.
I don't see the reason in phpunit console.
I'am wrong If i say that the html part that is hacked into the console in your video are fragments from the 500 error page raised by Symfony when something wrong happen? If that is the case not only onNoSuccessfulTest method must be changed to match the signature of the new onNoSuccessfulTest phpunit method, but in case of Symfony4 to match the behaviour from these video debugResponse method must be changed because 500 error page is not the same as before. I triggered the same exception on web part and I inspected the 500 html page that was raised The methods that i write in previous asking is intending to extract only I considered to be the important part.
Obviously something eluded me because on API side in phpunit cosole I see only that ProgrammerControllerTest::testPOST method fails. That's not an eror. But I don't see the reason of this failure in console.
Where I am wrong?
The actual code would be more helpfull? I have a git repository with all steps that i made in cronological order from the creation of the entities with bin/console make to changes I made in controllers and forms and so on
testPOST method give the correct message failure, there's no errors, only the failure message, so it works with modifications i specified, but is not helpful enough because doesn't have any explanatory lines like in your video.
I hope you excuse me. I don't want to torment you.
Cheers and Happy new year

Reply

Hey Diaconescu!

Yes, I think I may be understand you :). Specifically the problems are:

> onNoSuccessfulTest method must be changed to match the signature of the new onNoSuccessfulTest phpunit method,

and

> the behaviour from these video debugResponse method must be changed because 500 error page is not the same as before

I can't confirm that you're 100% correct about these, but they both make sense :). If you're able to push your example code, that *would* indeed help us. Also, have you been able to change the debugResponse() method to work with the new markup? Or are you having some issues with that?

Cheers!

Reply
Diaconescu Avatar

Cheers!
I think I resolved debugResponse() method. I raised the same kind error on web interface and I hack the markup I found there. To raise this error I replace $programmer->setUser($this->getUser()) in new ProgrammerControoler method with $programmer->setUser('nnn') and I inspected the markup to see how it looks like.
You may see the actual code at https://github.com/petre-sy.... Dwnload the zip archive from there.
I tried to describe precisely in the last five commits what I reused from your code and what I replaced and also a little debugging. I hope is nothing ambiguous now.
Thank you.

Did you find the repo? You can download the code?

1 Reply

Hey Diaconescu!

Ah, it makes perfect sense! Nice work - and thanks for doing that :). I CAN see the repo - hopefully it will help other people. I see that most of the fixes were on this commit: https://github.com/petre-sy...

Cheers!

Reply
Diaconescu Avatar

Do you see any fix to correct the problem that this code has?

Reply

Hi Diaconescu!

Ah, I think I misunderstood you! Apologies! I thought you were telling me that you DID successfully "update" the code in that GitHub repository for the new exception page format. But, it sounds like that is not true. With your modified code, you are still not seeing the errors correctly in your terminal, is that correct? Do you see anything at all? Or still just an error? I can definitely look into helping :).

Cheers!

Reply
Diaconescu Avatar

I added recently two new commits because I added the new panther browser test to the mix.
I don't see anything at all. Is just the normal failing test message. like this:
Testing Project Test Suite
array(0) {
}
F 1 / 1 (100%)

Time: 2.33 seconds, Memory: 30.00MB

There was 1 failure:

1) App\Tests\Controller\Api\ProgrammerControllerTest::testPOST
Failed asserting that 500 matches expected 201.

/home/petrero/www/Symfony/RestAPISite/tests/Controller/Api/ProgrammerControllerTest.php:27

There's no trace of 500 error page here. Indeed in console where run panther browser appear the reason respectivelly: Uncaught PHP Exception Doctrine\DBAL\Exception\NotNullConstraintViolationException: "An exception occurred while executing 'INSERT INTO programmer (nickname, avatar_number, tag_line, power_level, user_id) VALUES (?, ?, ?, ?, ?)' with params ["ObjectOrienter", 5, "

1 Reply
Diaconescu Avatar

I added two new commits.
I have only the failing normal message that phpunit normally give. I don't see which line in ProgrammerController is the one that makes the code to explode. In other words i don't see anything that's scrapped from 500 symfony error page.

Reply

Hi again Diaconescu!

Sorry for being slow! I've just cloned your code and got it working - you already did all the hard work and were very close! Here is the PR: https://github.com/petre-symfony/RestApiSymfony4/pull/1

I really only needed to do 2 things:

1) In the test function, you were instantiating your own Client object. You need to use $this->client instead. Otherwise, the static history property will never be populated in ApiTestCase and so we don't have access to the Response to print it.

2) Once I did this, onNotSuccessfulTest was successfully being called and the $lastResponse variable was set. But THEN I got an error that the symfony/css-selector is not installed. Indeed, inside the debugResponse function, we're using some code with the $crawler that needs the symfony/css-selector to be installed. Once I did that, it all worked!

Let me know if this makes sense!

Cheers!

Reply
Diaconescu Avatar

As soon as my eyes landed on my test code maked perfect sense. That was a very infurating mistake. I was so entranched in APITest code that I saw nothing else.
Thank you. It works now, indeed

Reply
Lijana Z. Avatar
Lijana Z. Avatar Lijana Z. | posted 5 years ago

where can we get that function which extract info from error with html ?

Reply

Hey Lijana Z.

You can find those debugging methods inside src/AppBundle/Test/ApiTestCase.php

Cheers!

1 Reply
Default user avatar

Hi all, my Phpstrom don't like these lines in ApiTestCase calss

use GuzzleHttp\Message\AbstractMessage;
use GuzzleHttp\Message\ResponseInterface;
use GuzzleHttp\Subscriber\History;

Reply

Hey Nacer,

What do you mean? Can't PhpStorm find these classes or these classes just are not used in the class? If the 2nd, i.e. you don't use those classes - just remove the namespaces. But if the first one - please, make sure you've installed dependencies with "composer install". Also, what version of Guzzle do you use? Probably you use a different major version, so those namespaces were changed and you need to use the new ones.

Cheers!

Reply
Default user avatar

I'm on GuzzleHttp 6, I' think the file in resources need to be updated

Reply

Hey Nacer!

Ah yes. So, this tutorial uses version *5* of Guzzle. But, before episode 4 (https://knpuniversity.com/s..., we upgraded to Guzzle 6. If you download the start code for that tutorial, you'll find an ApiTestCase that works with version 6 :).

Cheers!

Reply
Default user avatar
Default user avatar Lipiluk | posted 5 years ago

I would like to ask, why are you using Guzzle instead of in-built Symfony's client/crawler? Are there any advantages or disadvantages of using those both? As far as I can see Symfony's offer programmers such an ability. Thank you for any reply.

Reply

Hi there!

Yea, great question! This is a personal preference of mine. The reason is that Guzzle is the standard in the PHP world for making HTTP/API requests. Symfony's client/crawler is quite good, but the crawler (specifically) is useful for crawling HTML pages - it doesn't serve you any purpose when making API requests. So, I usually think it makes more sense to use Guzzle, since you'll probably also use it in the real-world to make API requests to other services.

However, there is one advantage that Symfony's client has over Guzzle. Because (with the Symfony client) you are *not* making real HTTP requests (you are making "fake" requests into Symfony's kernel), you can potentially do 2 interesting things. First, you could change some setting in the container right in your test class, make the request class, and your code will use that setting. Second, and probably more interesting, you can turn on the profiler and get information from the profiler (e.g. "was an email sent?"). That's not enough for me to want to use it however :).

Thanks for the question - hope that clarifies!

Cheers!

Reply
Cat in space

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

This tutorial uses an older version of Symfony. The concepts of REST are still valid, but I recommend using API Platform in new Symfony apps.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3",
        "symfony/symfony": "2.6.*", // v2.6.11
        "doctrine/orm": "~2.2,>=2.2.3,<2.5", // v2.4.7
        "doctrine/dbal": "<2.5", // v2.4.4
        "doctrine/doctrine-bundle": "~1.2", // v1.4.0
        "twig/extensions": "~1.0", // v1.2.0
        "symfony/assetic-bundle": "~2.3", // v2.6.1
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.8
        "symfony/monolog-bundle": "~2.4", // v2.7.1
        "sensio/distribution-bundle": "~3.0,>=3.0.12", // v3.0.21
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2", // v3.0.7
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.0
        "hautelook/alice-bundle": "0.2.*", // 0.2
        "jms/serializer-bundle": "0.13.*" // 0.13.0
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3", // v2.5.3
        "behat/behat": "~3.0", // v3.0.15
        "behat/mink-extension": "~2.0.1", // v2.0.1
        "behat/mink-goutte-driver": "~1.1.0", // v1.1.0
        "behat/mink-selenium2-driver": "~1.2.0", // v1.2.0
        "phpunit/phpunit": "~4.6.0" // 4.6.4
    }
}
userVoice