Jan 122012
 

Here is the workflow I have to add a new function in an entity repository. Let’s say I have a blog and I want to get the most seen posts by counting the visits for each post. Here is the workflow I have:

Write the functions structure in the repository
Write the test structure test class
Write the DQL query with the console
Write the test in the test class
Write the query in the repository

1
The usual way to do add a query in a repository to add a new function that query the database and return the results:

namespace Acme\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
 
class PostRepository extends EntityRepository {
    public function getMostSeen() {
        // query database and return results
    }
}

But this is hard to debug. I prefer to have for each query, two functions: one that build the query (that can easily be tested and debugged) and another one that execute that query and return result:

namespace Acme\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
 
class PostRepository extends EntityRepository {
    public function qbMostSeen() {
        return $this->createQueryBuilder('p')
                    -> // ...
    }
 
    public function getMostSeen() {
        return $this->qbMostSeen()
                    ->getQuery()
                    ->getResult();
    }
}

2
The test class will be in the Tests directory of the bundle:

namespace Acme\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class PostRespositoryTest extends WebTestCase {
    // ...
}

But we want to be able to instantiate PostRepository and for that we need the Entity Manager and a kernel (that we don’t have by default). So let’s add setUp() that will be executed before the tests:

namespace Acme\BlogBundle\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
 
class PostRespositoryTest extends WebTestCase {
    private $repo;
 
    public function setUp() {
        $kernel = static::createKernel();
        $this->repo = $kernel->boot();
        $this->repo = $kernel->getContainer()
                             ->get('doctrine.orm.entity_manager')
                             ->getRepository('AcmeBlogBundle:Post');
    }
}

We add our tests:
on the query itself: here we compare the query DQL to the DQL we are going to write
on the results: here we check that we have some results

public function testMostSeen()
{
    $this->assertEquals(
        $this->repo->qbMostSeen()->getDql(),
        "..." // the DQL query
    );
 
    $this->assertNotEquals(0, count($this->repo->getMostSeen()));
}

You can add your tests on either the query or the results.
3
Write the DQL. Let’s write the DQL step by step testing each step with the console. Here, we get all the posts:

app/console doctrine:query:dql --max-result=5 "SELECT p.title FROM Acme\BlogBundle\Entity\Post p"

then we add one by one parts of our query, testing on our database to check the result every time. A left join to get all the visits:

SELECT p.title, v.id
FROM Acme\BlogBundle\Entity\Post p
LEFT JOIN p.visits v

then we add the visit count:

SELECT p.title, COUNT(v) AS visit_count
FROM Acme\BlogBundle\Entity\Post p
LEFT JOIN p.visits v
GROUP BY p.title

and we order by visit count:

SELECT p.title, COUNT(v) AS visit_count
FROM Acme\BlogBundle\Entity\Post p
LEFT JOIN p.visits v
GROUP BY p.title
ORDER BY visit_count DESC

Now that we are happy with the query, we can finish to write our tests.
4
Just insert the DQL in the test:

$this->assertEquals(
    $this->repo->qbMostSeen()->getDql(),
    "SELECT p.title, COUNT(v) AS visit_count FROM Acme\BlogBundle\Entity\Post p LEFT JOIN p.visits v GROUP BY p.title ORDER BY visit_count DESC"
);

5
Finally, write the query until PHPUnit is green!

namespace Acme\BlogBundle\Repository;
use Doctrine\ORM\EntityRepository;
 
class PostRepository extends EntityRepository {
    public function qbMostSeen() {
        return $this->createQueryBuilder('p')
                    ->select('p.title', 'COUNT(v) AS visit_counts')
                    ->leftJoin('p.visits', 'v')
                    ->orderBy('visit_counts', 'DESC');
    }
 
    public function getMostSeen() {
        return $this->qbMostSeen()
                    ->getQuery()
                    ->getResult();
    }
}


Pro tip

You might want to add the alias dql to your shell environment (.bash_profile, .zsh/zshaliases or whatever):

alias dql="app/console doctrine:query:dql"

so you can just run in your shell:

dql "SELECT p.title FROM Acme\BlogBundle\Entity\Post p"

Feel free to ask questions right here

Thanks to the origin author here.

Jan 122012
 

First of all, install PHPUnit:


$ pear channel-discover pear.phpunit.de
$ pear channel-discover components.ez.no
$ pear channel-discover pear.symfony-project.com
$ pear install phpunit/PHPUnit

Then you can start to use PHP Unit like:


$phpunit -c app/

If you want to get report on HTML format, add parameter:


$ phpunit -c app/ --coverage-html=cov/

Not everybody has xdebug installed, maybe you will meet some problems like:

The Xdebug extension is not loaded. No code coverage will be generated.

That means you need to install xdebug for your PHP5. If you are using OpenSuse, it has a PHP Extensions repository, you can find more details from the following site:


http://download.opensuse.org/repositories/

and

http://download.opensuse.org/repositories/server:/php:/extensions/openSUSE_12.1/server:php:extensions.repo

You just need to install it simply as:

$sudo zypper ar  http://download.opensuse.org/repositories/server:/php:/extensions/openSUSE_12.1/server:php:extensions.repo

$sudo zypper install php5-xdebug

Now you fix the problem.

Start

OK, Let’s start to mark some tips about unit test for Symfony2.

First, a simple unit test:

// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
namespace Acme\DemoBundle\Tests\Utility;

use Acme\DemoBundle\Utility\Calculator;

class CalculatorTest extends \PHPUnit_Framework_TestCase
{
public function testAdd()
{
$calc = new Calculator();
$result = $calc->add(30, 12);

// assert that our calculator added the numbers correctly!
$this->assertEquals(42, $result);
}
}

You can run unit test via:

# run all tests in the Utility directory
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/

# run tests for the Calculator class
$ phpunit -c app src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php

# run all tests for the entire Bundle
$ phpunit -c app src/Acme/DemoBundle/

What about a functional unit test?

// src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php
namespace Acme\DemoBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class DemoControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient();

$crawler = $client->request('GET', '/demo/hello/Fabien');

$this->assertTrue($crawler->filter('html:contains("Hello Fabien")')->count() > 0);
}
}

Something more about links:

$link = $crawler->filter('a:contains("Greet")')->eq(1)->link();

$crawler = $client->click($link);

And form:


$form = $crawler->selectButton('submit')->form();

// set some values
$form['name'] = 'Lucas';
$form['form_name[subject]'] = 'Hey there!';

// submit the form
$crawler = $client->submit($form);

Now that you can easily navigate through an application, use assertions to test that it actually does what you expect it to. Use the Crawler to make assertions on the DOM:

// Assert that the response matches a given CSS selector.
$this->assertTrue($crawler->filter('h1')->count() > 0);

Or, test against the Response content directly if you just want to assert that the content contains some text, or if the Response is not an XML/HTML document:

$this->assertRegExp('/Hello Fabien/', $client->getResponse()->getContent());

The test Client simulates an HTTP client like a browser and makes requests into your Symfony2 application:

$crawler = $client->request('GET', '/hello/Fabien');

The request() method takes the HTTP method and a URL as arguments and returns a Crawler instance.

Use the Crawler to find DOM elements in the Response. These elements can then be used to click on links and submit forms:

$link = $crawler->selectLink('Go elsewhere...')->link();
$crawler = $client->click($link);

$form = $crawler->selectButton('validate')->form();
$crawler = $client->submit($form, array('name' => 'Fabien'));

The click() and submit() methods both return a Crawler object. These methods are the best way to browse your application as it takes care of a lot of things for you, like detecting the HTTP method from a form and giving you a nice API for uploading files.

The request method can also be used to simulate form submissions directly or perform more complex requests:

// Directly submit a form (but using the Crawler is easier!)
$client->request('POST', '/submit', array('name' => 'Fabien'));

// Form submission with a file upload
use Symfony\Component\HttpFoundation\File\UploadedFile;

$photo = new UploadedFile(
'/path/to/photo.jpg',
'photo.jpg',
'image/jpeg',
123
);
// or
$photo = array(
'tmp_name' => '/path/to/photo.jpg',
'name' => 'photo.jpg',
'type' => 'image/jpeg',
'size' => 123,
'error' => UPLOAD_ERR_OK
);
$client->request(
'POST',
'/submit',
array('name' => 'Fabien'),
array('photo' => $photo)
);

// Perform a DELETE requests, and pass HTTP headers
$client->request(
'DELETE',
'/post/12',
array(),
array(),
array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word')
);

Last but not least, you can force each request to be executed in its own PHP process to avoid any side-effects when working with several clients in the same script:

$client->insulate();

Browsing

The Client supports many operations that can be done in a real browser:


$client->back();
$client->forward();
$client->reload();

// Clears all cookies and the history
$client->restart();
Accessing Internal Objects

If you use the client to test your application, you might want to access the client’s internal objects:


$history = $client->getHistory();
$cookieJar = $client->getCookieJar();

You can also get the objects related to the latest request:


$request = $client->getRequest();
$response = $client->getResponse();
$crawler = $client->getCrawler();

If your requests are not insulated, you can also access the Container and the Kernel:

$container = $client->getContainer();
$kernel = $client->getKernel();

Accessing the Container

It’s highly recommended that a functional test only tests the Response. But under certain very rare circumstances, you might want to access some internal objects to write assertions. In such cases, you can access the dependency injection container:


$container = $client->getContainer();

Be warned that this does not work if you insulate the client or if you use an HTTP layer. For a list of services available in your application, use the container:debug console task.

 

Something more details you should refer to the official symfony book from here.