How to set up database-heavy unit tests in Symfony2 using PHPUnit?

k0pernikus picture k0pernikus · May 28, 2012 · Viewed 14.8k times · Source

I am quite new to the world of testing and I want to make sure I am on the right track.

I am trying to setup unit tests in a symfony2 project using phpunit.

PHPUnit is working and the simple default controller tests work fine. (Yet this is not about functional testing but unit testing my application.)

My project relies heavily on database interactions though, and as far as I understand from phpunit's documentation, I should set up a class based on \PHPUnit_Extensions_Database_TestCase, then create fixtures for my db and work from there.

Yet, symfony2 only offers a WebTestCase class which only extends from \PHPUnit_Framework_TestCase out of the box.

So am I right to assume that I should create my own DataBaseTestCase which mostly copies WebTestCase, only difference being that it extends from \PHPUnit_Extensions_Database_TestCase and implements all its abstract methods?

Or is there another "built-in" recommended workflow for symfony2 concerning database-centric tests?

As I want to make sure that my models store and retrieve the right data, I do not want to end up testing the specifics of doctrine by accident.

Answer

Sgoettschkes picture Sgoettschkes · Sep 20, 2012

I have never used the PHPUnit_Extensions_Database_TestCase, mostly because for these two reasons:

  • It doesn't scale well. If you set up and tear down the database for every single test and you have a application which relies heavily on the database, you end up creating and dropping the same schema over and over again.
  • I like to have my fixtures not only within my tests but also within my development database and some fixtures are even needed for production (initial admin user or product categories or whatever). Having them inside an xml which can only be used for phpunit doesn't seem right to me.

My way in theory...

I use the doctrine/doctrine-fixtures-bundle for fixtures (no matter what purpose) and set up the whole database with all fixtures. I then execute all tests against this database and make sure to recreate the database if a test changed it.

The advantages are that I don't need to set up a database again if a test only reads but don't change anything. For changes I have to do drop it and create it again or make sure to revert the changes.

I use sqlite for testing because I can set up the database, then copy the sqlite file and replace it with a clean one to bring back the original database. That way I don't have to drop the database, create it and load all fixtures again for a clean database.

...and in code

I wrote an article about how I do database tests with symfony2 and phpunit.

Although it uses sqlite I think one can easily make the changes to use MySQL or Postgres or whatever.

Thinking further

Here are some other ideas which might work:

  • I once read about a test setup where before you use the database you start a transaction (within the setUp method) and then use the tearDown to rollback. That way you don't need to set up the database again and just need to initialize it once.
  • My setup described above has the drawback that the database is set up every time phpunit is executed, even if you only run some unit tests with no database interaction. I am experimenting with a setup where I use a global variable which indicates if the database was set up and then within the tests call a method whcih checks this variable and initializes the database if it didn't happened yet. That way only when a tests needs the database the setup would happen.
  • One problem with sqlite is that it doesn't work the same as MySQL in some rare cases. I had an issue once where something behaved different in MySQL and sqlite causing a test to fail when in with MySQL everything worked. I cannot remember what it was exactly.