Create a manual symfony 2 request and response object from a command - php

I'm attempting to write a symfony 2 command which can basically read in a number of routes (either through a yml file or through arguments) and can go and get the response for each of these pages so I can report back whether they came back as 200/404/502 etc..
The routes are relative so would be routes such as '/' and '/news'.
Can't seem to work out how to send these requests through to get a real response, I can use Request::create() to create a request, but this doesn't seem to work how I want it to.
Do I have to go through the Kernel even though its a command? Any help would be appreciated.
What I have so far:
$request = Request::create('/news', 'GET');
$response = new Response();
$response->prepare($request);
$res = $response->send();
var_dump($res->getContent());
This comes back with an empty string all the time.
Also tried the following:
$client = new Client(
new HttpKernel(new EventDispatcher(), new ControllerResolver())
);
$client->request('GET', '/news');
var_dump($client->getResponse());
Which comes back with a route is wrongly configured error
Thanks
Kevin

According to your comment:
Basically im testing the code base to ensure its all set up ok before I release it to the public, so I want to simulate a client request to the code and ensure I get a 200 response back, if a 404 came back then I know there is a problem so to pause the release.
This is easy to do with a test:
<?php
// tests/AppBundle/Controller/PostControllerTest.php
namespace Tests\AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PostControllerTest extends WebTestCase
{
public function testNews()
{
$client = static::createClient();
$crawler = $client->request('GET', '/news');
// Assert a specific 200 status code
$this->assertEquals(
200, // or Symfony\Component\HttpFoundation\Response::HTTP_OK
$client->getResponse()->getStatusCode()
);
}
}
You also need to install PHPUnit:
composer require --dev "phpunit/phpunit=5.4.*"
Then you can launch the tests:
php ./vendor/bin/phpunit -c app/phpunit.xml.dist
You'll have a result like this:
PHPUnit 5.4.6 by Sebastian Bergmann and contributors.
..................... 21 / 21 (100%)
Time: 5.29 seconds, Memory: 58.00MB
OK (21 tests, 149 assertions)

The best way to do it is using an http client to which transfer the responsibility to create a request object, perform the request, and return a response.
One of the most used is the guzzle http client (http://docs.guzzlephp.org/en/latest/)
So in your command you can get the router from the container, generate the url for that, and perform the request with guzzle client.
EDIT after comments:
To reach that goal to perform request without actually have a server running, you can use \Symfony\Bundle\FrameworkBundle\Client object that uses the kernel to perform request-response ( you have of course to inject the current kernel of your command). Pretty much as the WebTestCase class does.
http://api.symfony.com/2.8/Symfony/Bundle/FrameworkBundle/Client.html
Hope this will help ! (I will update with some code later)

Related

Laravel remembers original response during http tests

Given the following pest test:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The above should fully work; however, the second request post('/courses')...
fails saying that:
Failed asserting that <...> contains "WebTechnologies".
If I remove the first request:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The test passes.
If I remove the second request instead:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
});
It also passes.
So why should the combination of the two cause them to fail? I feel Laravel is caching the original response, but I can't find anything within the documentation supporting this claim.
I have created an issue about this on Laravel/Sanctum as my problem was about authentication an stuff...
https://github.com/laravel/sanctum/issues/377
One of the maintainers of Laravel Said:
You can't perform two HTTP requests in the same test method. That's not supported.
I would have wanted a much clearer explanation on why it's not supported.
but I guess, we would never know. (Unless we dive deep into the Laravel framework and trace the request)
UPDATE:
My guess is that, knowing how Laravel works, for each REAL request Laravel initializes a new instance of the APP...
but when it comes to Test, Laravel Initializes the APP for each Test case NOT for each request, There for making the second request not valid.
here is the file that creates the request when doing a test...
vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php
it's on the call method line: 526 (Laravel v9.26.1)
as you can see...
Laravel only uses 1 app instance... not rebuilding the app...
Line 528: $kernel = $this->app->make(HttpKernel::class);
https://laravel.com/docs/9.x/container#the-make-method
the $kernel Variable is an instance of vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
My guess here is that the HttpKernel::class is a singleton.
P.S. I can do a little more deep dive, but I've procrastinated too much already by answering this question, it was fun thou.
TL;DR.
You can't perform two HTTP requests in the same test method. That's not supported.
UPDATE:
I was not able to stop myself...
I found Laravel initializing Kernel as a singleton
/{probject_dir}/bootstrap/app.php:29-32
Please make sure to not use any classic singleton pattern which isn't invoked with singleton binding or facades.
https://laravel.com/docs/9.x/container#binding-a-singleton
$this->app->singleton(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
The Laravel app won't be completely restarted during tests unlike different incoming HTTP requests - even if you call different API endpoints in your tests

How do I make a guzzle response into a response to a request from a client?

I have an application running on webserver A. I have a second application running on webserver B. Both webservers require a login. What I need to do is have a request to webserver A pass through to webserver B and return a file to the client without having the client login to Webserver B. (In other words, webserver B will be invisible to the client and I will take care of the auth credentials with my request to B from A). The code below is built on a laravel framework, but I don't believe the answer needs to be laravel specific.
The code works but it is only returning the HEAD information of the file to the calling client. Not the file itself.
Any help will be greatly appreciated!
Controller:
public function getAudioFile(Request $request)
{
//This is the id we are looking to pull
$uid = $request->uniqueid;
$audioServices = new AudioServices();
return $audioServices->getWavFile($uid);
}
Service:
public function getWavFile(String $uniqueId)
{
$client = new GuzzleHttp\Client(['verify' => false]);
return $client->request('GET', $this->connectString.$uniqueId, ['auth' => ['username', 'password']]);
}
As mentioned by bishop you can use sink option from Guzzle to stream the response of a Guzzle request.
You can pass that stream to a response from your controller. I'm not sure if Laravel has built-in stream support, but the underlying symfony httpfoundation components do. An example of it's usage can be found in this tutorial.
If you prefer not to use the sink option from Guzzle you can also use the response itself as that implements PSR-7 stream objects.

symfony2 phpunit - functional testing error (host_with_path)

So thanks to Matteo (phpunit in symfony2 - No tests executed) I can now test my functional tests.
Now I got the following error when running phpunit -c app:
You must change the main Request object in the front controller (app.php)
in order to use the `host_with_path` strategy.
so I did change it in the app.php, from:
$request = RequestFactory::createFromGlobals('host_with_path');
to:
$request = Request::createFromGlobals();
I also updated my swiftmailer-bundle from version 2.3 to 5.4.0.
Unfortunately This did not fix my error.
and this is my ../app/config_test.yml
swiftmailer:
disable_delivery: true
Am I missing something here?
I cannot seem to find this error anywhere on the web. Does someone know how I should fix this error?
After some searching I noticed that the app.php wasn't the problem. It was the DefaultControllerTest.php. The error could be fixed by removing the following lines from the DefaultControllerTest:
$crawler = $client->request('GET', '/hello/Fabien');
$this->assertTrue($crawler->filter('html:contains("Hello Fabien")')->count() > 0);
Due to recent developments our development team decided to stop using Sonata. As a side effect this bug got fixed. So I won't have a solution for this problem.
The problem here is, that the Client object is using neither app.php nor app_dev.php.
The client creates the request internally. So it won't be the request you need.
The only solution I can see is to override the method Symfony\Bundle\FrameworkBundle\Test\WebTestCase::createClient to return your own client. This client is than responsible for creating the actual request object. The following is the current behavior.
namespace Symfony\Component\HttpKernel;
use Symfony\Component\BrowserKit\Client as BaseClient;
class Client extends BaseClient
{
...
/**
* Converts the BrowserKit request to a HttpKernel request.
*
* #param DomRequest $request A DomRequest instance
*
* #return Request A Request instance
*/
protected function filterRequest(DomRequest $request)
{
$httpRequest = Request::create($request->getUri(), $request->getMethod(), $request->getParameters(), $request->getCookies(), $request->getFiles(), $request->getServer(), $request->getContent());
foreach ($this->filterFiles($httpRequest->files->all()) as $key => $value) {
$httpRequest->files->set($key, $value);
}
return $httpRequest;
}
...
}
You have to override method filterRequest to return kind of a request you want.

symfony 2 and phpunit crawler client does not reach ngnix

I have written this simple test:
<?php
namespace Hello\ApiBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class HelloControllerTest extends WebTestCase {
public function test() {
$client = static::createClient();
$crawler = $client->request("GET","http://localhost/hello");
$response = $client->getResponse()->getStatusCode();
var_dump($response);
}
}
?>
When I am running this test, it prints a 404 status code.
Oddly, I don't see the request on ngninx access log. Even if I change the URL to "/hello" it still looks like the request isn't reaching the local webserver.
Needless to say it works if I just open Chrome and try this URL (http://localhost/hello) normally.
What am I missing?
It's because Symfony's test framework actually only simulates request (and run dispatcher directly behind). It doesn't send a real request.
When you are testing your app, use relative paths:
$crawler = $client->request("GET","/hello");

Mock a request in phpunit

I am trying to write a unit test (phpunit) to cover a controller action. I am receiving problems regarding invalid scope for the getRequest() call.
note: I am a newbie to Symfony2 and TDD (phpunit)
I guess this is because there is no request as such?
My questions are:
Can I mock a request object?
Am I approaching this in the right way? Should I be placing the bulk of the code into a service and then unit testing the service and only FUNCTIONAL test the controllers?
I think knowing the principle going forward is what I'm after, rather than the lines of code.
There is a mock request built into the web test case. Just extend that and use the crawler to make the request.
For example:
public function testMyController()
{
$client = static::createClient();
$router = $this->container->get('router');
$url = $router->generate('routeName');
$crawler = $client->request('GET', $url);
// check we get a 200
$this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for API Config call");
}

Categories