I am new to phpunit testing and did a very simple test looking for status code.
The test passes when I run:
bin\phpunit -c app src\AppBundle\Tests\Controller\StarLinX\TravelControllerTest.php
PHPUnit 4.6.10 by Sebastian Bergmann and contributors.
Configuration read from C:\PhpstormProjects\dir\app\phpunit.xml.dist
.
Time: 6.03 seconds, Memory: 20.00Mb
OK (1 test, 1 assertion)
But when I load the page in the browser, an exception is thrown rendering the twig file with status code 500.
I thought maybe this was a cache issue, so I cleared cache in --env=dev, prod and test.
How do I troubleshoot this error?
This is my test file:
namespace AppBundle\Tests\Controller\StarLinX;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class TravelControllerTest extends WebTestCase {
public function testGET() {
// Create a new client to browse the application
$client = static::createClient();
// get the page
$crawler = $client->request('GET', '/travel/aaaaa');
$this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /travel/aaaaa");
}
}
This is the error that is thrown when running in the dev environment:
An exception has been thrown during the rendering of a template ("Notice: Array to string conversion")
So after a little more analysis, I find that the error around {{ weatherInfo }} which should be {{ weatherInfo.now }}. This throws an error when running the development environment. In production, twig simply displays Array.
Is that normal behavior?
As it's written your test only check the error status, the test is not specific enough. What if it displayed another page? The test would still pass.
You should add some other tests in order to ensure that the page is properly displayed:
// …
$this->assertEquals(200, $client->getResponse()->getStatusCode(),
"Unexpected HTTP status code for GET /travel/aaaaa");
// Check that the page has a title.
$this->assertSame(
1,
$crawler->filter('title')->count()
);
// Check that the page has a correct title.
$this->assertSame(
'Travel',
$crawler->filter('title')->text()
);
// Check something in the content
$this->assertSame(
'Hello, World!',
$crawler->filter('body > div#content')->text()
);
If you don't have enough information to debug your code, tou can access to the page content which usually contain the error message:
die($this->client->getResponse()->getContent());
Related
I have a custom command in my symfony project to populate the database with the default data that the application need to work in both dev and prod environments.
For the dev environment I have a fixture script that depends on these default common data.
I'm trying to call my custom Symfony command in the fixture script so that I'm sure to have the required data to properly load my fixtures.
This is my custom command app:db:populate in "pseudo script", just creating a bunch of entities, persit & flush. My custom command works fine when I call it through php bin/console app:db:populate
protected function execute(InputInterface $input, OutputInterface $output)
{
// Creating a bunch of default entities, persist them and flush
$data = new MyDefaultEntity();
// ...
$this->manager->persist($data);
// ...
$this->manager->flush();
}
Then, in my fixture script, I want to call app:db:populate first, because fixtures depends on these data. So I tried to use the Process class to execute my script this way :
public function load(ObjectManager $manager)
{
// Execute the custom command
$cmd = 'php bin/console app:db:populate';
$process = new Process($cmd);
$process->run(function ($type, $buffer) {
if (Process::ERR === $type) {
echo 'ERR > '.$buffer;
} else {
echo 'OUT > '.$buffer;
}
});
// Then load the fixtures !
// ...
}
The custom command seems to execute well until the $this->manager->flush();
I have the following error in my console (Data is obfuscated for the post):
In AbstractMySQLDriver.php line 36:
An exception occurred while executing 'INSERT INTO ....(..., ..., ...) VALUES (?, ?, ?)' with params ["...", "...", "..."]:
SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction
I don't know what to do regarding this error ... Why the command is working normally when used through a classic console call and why it is not working in a Process?
So, the short answer is
Quoting Symfony documentation :
You may have the need to execute some function that is only available in a console command. Usually, you should refactor the command and move some logic into a service that can be reused in the controller.
I ended up making a service class that handles all the app:db:populate logic (read a json file and insert basic app entities in the database). Then I call this service in both app:db:populate execute methods and AppFixtures load methods.
Hope this will help someone.
My app starts locally by http : //sm1/app/web/app_dev.php (symfony3).
PHPUnit test has been build by framework.
namespace Tests\AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class GoodControllerTest extends WebTestCase
{
public function testCompleteScenario()
{
// Create a new client to browse the application
$client = static::createClient([], ['HTTP_HOST' => 'sm1']);
// Create a new entry in the database
$crawler = $client->request('GET', '/good/');
$this->assertEquals(200,
$client->getResponse()->getStatusCode(),
"Unexpected HTTP status code for GET /good/"
);
$crawler = $client->click(
$crawler->selectLink('Create a new entry')->link());
But after test running I've got an error
There was 1 error:
1) Tests\AppBundle\Controller\GoodControllerTest::testCompleteScenario
InvalidArgumentException: The current node list is empty.
/home/sm1/www/app/vendor/symfony/symfony/src/Symfony/Component/DomCrawler/Crawler.php:735
/home/sm1/www/app/tests/AppBundle/Controller/GoodControllerTest.php:18
and in a log file this message:
request.INFO: Matched route "{route}".
{"route":"good_index","route_parameters":
{"_controller":"AppBundle\Controller\GoodController::indexAction",
"_route":"good_index"},
"request_uri":"http://sm1/good/","method":"GET"} []
How to fix "request_uri" from "http : //sm1/good/"
to "http : //sm1/app/web/app_dev.php/good/" ?
It is necessary to start web app only by http://app. That is, to use PHPUnit you need a virtual host. If the name of the input script is different from the default, note this in the file .htaccess.
Since I migrated to Symfony 2.4, I'm getting the following error message :
Rendering a fragment can only be done when handling a Request.
It's happening because, on some pages, I'm rendering some templates with Twig inside some pages which are handled by another older framework, by doing $sf2->container->get('twig')->render("MyBundle::my-template.html.twig");.
So yes, that's right that Symfony 2 isn't handling those requests, but I still want to render those templates with Twig ! Why can't I do that (anymore) ?! And how to fix that ?
Here is the code I'm executing to "boot" SF2 in my older project :
$loader = require_once __DIR__.'/../../../app/bootstrap.php.cache';
Debug::enable();
require_once __DIR__.'/../../../app/AppKernel.php';
$kernel = new AppKernel('bootstrap', true);
$kernel->loadClassCache();
Request::enableHttpMethodParameterOverride();
$request = Request::createFromGlobals();
$kernel->boot();
$kernel->getContainer()->enterScope('request');
$kernel->getContainer()->set('request', $request, 'request');
$this->container = $kernel->getContainer();
EDIT : By the way, it might be related to that : Symfony 2.4 Rendering a controller in TWIG throws "Rendering a fragment can only be done when handling a Request." Exception .
Though, I don't want to downgrade to Symfony 2.3, and deleting the vendor directory didn't fix my problem.
EDIT² : I found out that the problem is because of the new RequestStack.
In HttpKernel, handleRaw(Request $request, $type = self::MASTER_REQUEST) normally push the request to the RequestStack ($this->requestStack->push($request);). So if I add a public method pushRequestStack($request) to the HttpKernel it works.. But how could I do it properly ? I don't find any public method able to get the $requestStack from HttpKernel (so I can push the request externally)..
And I can't use the "normal" method ($kernel->handle($request)) because it would throw some exceptions, for example for the route that doesn't exist, or also for the session that has already been started by PHP..
In conclusion, is there any way to "push" my/any request to the requestStack without completly handling the request ?
You have to push a new Request in the "request_stack" from your Sf2 command.
$this->getContainer()->get('request_stack')->push(Request::createFromGlobals());
I had the same problem and solved this by rebuilding the bootstrap with this command:
php vendor/bundles/Sensio/Bundle/DistributionBundle/Resources/bin/build_bootstrap.php app
When I run even a sample test, either in Netbeans IDE of from the command line, e.g.:
class WebTest extends PHPUnit_Extensions_Selenium2TestCase
{
protected function setUp()
{
$this->setBrowser('firefox');
$this->setBrowserUrl('http://www.example.com/');
}
public function testTitle()
{
$this->url('http://www.example.com/');
$this->assertEquals('Example WWW Page', $this->title());
}
}
I get the PHPUnit response, but it doesn't seem to be communication with the Selenium server as no browser window was created.
PHPUnit 3.6.10 by Sebastian Bergmann.
Configuration read from /var/www/gcd/framework/yii/gadget/protected/tests/phpunit.xml
F
Time: 1 second, Memory: 10.50Mb
There was 1 failure:
1) WebTest::testTitle
Failed asserting that null matches expected 'Example WWW Page'.
Using PHPUnit 3.6.10 and selenium-server-standalone-2.21.0
Anyone any ideas?
Turns out, if you run PHPUnit with sudo, it works! Doh!
I want to add log output to all test messages.
$this->assertTrue(FALSE, "This assertion is False.". myLog::GetErrors());
I tried extending the PHPUnit_Framework_TestCase::assertThat function with my appended message, but it doesn't seem to have an effect on it. I know the extended PHPUnit_Framework_TestCase works because I have several helper functions (random string generation) in there as well that I use for testing.
Any other thoughts? Could it be a customer listener?
All of the assertions are static methods from PHPUnit_Framework_Assert and cannot be overridden by a subclass of TestCase. You can define your own assertions that call the originals with an amended message, however.
public static function assertTrue($value, $message='') {
PHPUnit_Framework_Assert::assertTrue($value, $message . myLog::GetErrors());
}
All failed tests call onNotSuccessfulTest() with the exception as the only parameter. You could override this method and in some cases add your log errors to the exception's message. Some of the PHPUnit exceptions provide a second description in addition to the error message contained in the exception.
public function onNotSuccessfulTest(Exception $e) {
if ($e instanceof PHPUnit_Framework_ExpectationFailedException) {
$e->setCustomMessage(implode(PHP_EOL,
array($e->getCustomMessage(), myLog::GetErrors())));
}
parent::onNotSuccessfulTest($e);
}
Update: As Gregory Lo noted in a comment below, setCustomMessage() and getCustomMessage() were removed in PHPUnit 3.6. :(
In recent PHPUnit Versions it is not necessary anymore.
When you specify the error parameter in an $this->assertXXXX() it preceeds PHPUnit's original message.
Screenshot: