One of the greatest things of testing Ruby on Rails applications was usage of Factories, managed by FactoryGirl. It changed the way the test data was managed in test. Instead of defining fixtures one by one, in a single .yml
configuration, it allowed to generate required models per test. This concept is very convenient, however it is not widespread in PHP world. Probably, this was because, there is no single ORM in PHP, like ActiveRecord is for Ruby. We have Doctrine, Eloquent, and each major framework has its own ORM layer, so that we it is pretty hard to deal with models of those frameworks in one manner.
But it looks like factories finally came to PHP! The League of Extraordinary Packages produced a great package, self-claimed as factory_girl for PHP. Meet Factory Muffin. It allows simple generation of database records for integration and functional testing. Let's see how it can be used with Codeception.
Setup in Codeception
At first lets add league/factory-muffin": "~2.0
to composer.json
:
"require-dev": {
"codeception/codeception": "~2.0",
"league/factory-muffin": "~2.0"
}
Then we execute composer install
.
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing fzaninotto/faker (v1.4.0)
Downloading: 100%
- Installing league/factory-muffin (v2.1.1)
Downloading: 100%
Writing lock file
Generating autoload files
Generating optimized class loader
As you may noticed, FactoryMuffin uses awesome Faker library for generating random data for models.
For using factories inside tests we will create FactoryHelper
:
php vendor/bin/codecept g:helper Factory
Helper /home/davert/project/tests/_support/FactoryHelper.php created
We will define factories in the _initialize
method of FactoryHelper
.
Let's say we have two models, Post
and User
in application. This is how we specify rules for generating new objects of these models.
<?php
class FactoryHelper extends \Codeception\Module
{
/**
* @var \League\FactoryMuffin\Factory
*/
protected $factory;
public function _initialize()
{
$this->factory = new \League\FactoryMuffin\Factory;
$this->factory->define('Post', array(
'title' => 'sentence|5', // title with random 5 words
'body' => 'text', // random text
));
$this->factory->define('User', array(
'email' => 'unique:email', // random unique email
));
}
}
?>
This is how generation of Post and User is defined. From the box, Muffin is designed to work with ActiveRecord models, and with Eloquent in particular. So you can use it in Yii, Phalcon, yet we will demonstrate its usage in Laravel application. FactoryMuffin can also be customized to work with Doctrine as well.
Using Factories in Laravel
To use FactoryMuffin in Laravel functional tests we need to create additional methods in FactoryHelper
for generating and saving records: havePosts
and haveUsers
methods. They will populate database with number of records specified. We will need to clean up those records at the end of a test.
<?php
class FactoryHelper extends \Codeception\Module
{
/**
* @var \League\FactoryMuffin\Factory
*/
protected $factory;
public function _initialize()
{
$this->factory = new \League\FactoryMuffin\Factory;
$this->factory->define('Post', array(
'title' => 'sentence|5', // title with random 5 words
'body' => 'text', // random text
));
$this->factory->define('User', array(
'email' => 'unique:email', // random unique email
));
}
public function havePosts($num)
{
$this->factory->seed($num, 'Post');
}
public function haveUsers($num)
{
$this->factory->seed($num, 'Post');
}
public function _after(\Codeception\TestCase $test)
{
// actually this is not needed
// if you use cleanup: true option
// in Laravel4 module
$this->factory->deleteSaved();
}
}
?>
By including FactoryHelper to the functional suite config, we can use it inside the actor ($I
) object.
This allows us to test features like pagination. For instance, we can check that only 20 posts are listed on a page:
<?php
$I = new FunctionalTester($scenario);
$I->wantTo('check that only 20 posts are listed');
$I->havePosts(40);
$I->amOnPage('/posts');
$I->seeNumberOfElements('.post', 20);
?>
Source code of this example is on GitHub.
Factories in Unit Tests
Factories can also be used in unit testing. Let's say user can create posts, and in order to optimize queries we are saving number of user posts in num_posts
column of users
table. We are going to test that this column is updated each time a new post by user is created.
We will need one more method added into FactoryHelper
class:
<?php
public function produce($model, $attributes = array())
{
return $this->factory->create($model, $attributes);
}
?>
After we include FactoryHelper
into unit suite we can use its methods in tests:
<?php
class UserTest extends \Codeception\TestCase\Test
{
/**
* @var UnitTester
*/
protected $tester;
function testCounterCache()
{
$user = $this->tester->produce('User');
$this->assertEquals(0, $user->num_posts);
$this->tester->produce('Post', ['user_id' => $user->id]);
$this->assertEquals(1, User::find($user->id)->num_posts);
$this->tester->produce('Post', ['user_id' => $user->id]);
$this->assertEquals(2, User::find($user->id)->num_posts);
}
}
?>
Conclusion
As you see, factories make your tests clean and expressive. Forget about managing test data manually, forget about fixtures. Use FactoryMuffin.