Generate your functional tests with PHPUnit

DI DIO Maximilien
Feb 10 · 3 min read

Hello ! Did you have to test a specific routes of your amazing Symfony project ? Like a group a GET which must returned 200 http status code ? Ok i’ve a solution for you !

So, imagine the context. You have to test a existing project built on top of Symfony with lot of bundles. Your job is to test if the back office seems to be ok. (200)

But you have an amazing number of routes to tests and you doesn’t have to spend time to verify the work of each other. To test differents routes, the documentation is pretty clear. With PHPUnit, you have to create a provider function to repeat a test for each route

// tests/ApplicationAvailabilityFunctionalTest.php
namespace App\Tests;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ApplicationAvailabilityFunctionalTest extends WebTestCase
{
/**
* @dataProvider urlProvider
*/
public function testPageIsSuccessful($url)
{
$client = self::createClient();
$client->request('GET', $url);

$this->assertTrue($client->getResponse()->isSuccessful());
}

public function urlProvider()
{
yield ['/'];
yield ['/posts'];
yield ['/post/fixture-post-1'];
yield ['/blog/category/fixture-category'];
yield ['/archives'];
// ...
}
}

But you have to set each route yourself. But in our scenario, we have lot of tests and probably a short deadline. However, according to the documentation, we can import the Symfony Router object to test a route by its name.

// ...
private $router; // consider that this holds the Symfony router service

public function testBlogArchives()
{
$client = self::createClient();
$url = $this->router->generate('blog_archives');
$client->request('GET', $url);

// ...
}

Okay ! By we have to set the name of each route. This solution is clearly better because the test is always ok even if you change the URL of the route. But i can accept this solution for our scenario.

The solution ? Use the Router to get all routes of your project. Then you can apply a filter to keep what you want to test.

class UserTest extends WebTestCase {
....
/**
*
@var ContainerInterface
*/
protected static
$container;

/**
*
@var array Route
*/
protected static
$routes;


private function cleanUrl(Route $route, bool $ssl) : string {
$http = $ssl ? 'https://' : 'http://';
return $http . $route->getHost() . $this->replaceId($route->getPath());
}

private function routes() {
$router = self::$container->get('router');
$collectionRoute = $router->getRouteCollection();
$routes = $collectionRoute->all();

self::$routes = array();

foreach ($routes as $key => $route) {
$isForAdmin = preg_match('/^pathofexile/', $key);
$methods = $route->getMethods();
$isGet = empty($methods) || in_array("GET", $methods);

if ($isForAdmin && $isGet) {
self::$routes[$key] = [
$this->cleanUrl($route, false),
Response::HTTP_OK
];
}
}
}

public function urlProvider() {
self::bootKernel();
self::$container = self::$kernel->getContainer();
self::routes();
return self::$routes;
}

/**
*
@dataProvider urlProvider
*/
public function
testGet(string $url, int $expected) {
$response = self::$client->get($url);
$this->assertEquals($response->getStatusCode(), $expected);

}
}

I call the bootKernel method inside the provider because… see this.

Okay but you can have some problems with this solutions to test routes with specifics parameters and you have to cover each case for each route. Your application must never send back an error 500 (server side error) if user provide wrong parameters. Stay aware about this method to test your project.

Thx for reading this and see you soon.

Protocod

stupid dev blog

DI DIO Maximilien

Written by

Backend developer

Protocod

Protocod

stupid dev blog

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade