Symfony 2: Unit Testing Many-to-One Validations on Entities


From what I can tell, the Symfony 2 cookbook does not have a decent example for handling entity validations and unit testing. I looked around a bit for a good sample and found a decent answer over on Stackoverflow. But outside of setting up a base class for handling future validation test classes, the sample was a little on the shallow side. This post will try to add a little more depth to that article as well as show an interesting way to ensure that you’re checking your Many-to-One relationships as well.

The Stackoverflow article’s example showed a base class that you can use to retrieve your validation service and use in inherited classes. Here’s the full class reiterated here:

<?php 
// BaseEntityTest.php 
namespace Ecommerce\ShoppingBundle\Tests\Entity; 
require_once __DIR__.'/../../../../../app/AppKernel.php';

class BaseEntityTest extends \PHPUnit_Framework_TestCase 
{ 	
    protected static $kernel; 	
    protected static $container; 	 	
    public static function setUpBeforeClass() 	
    { 		
      self::$kernel = new \AppKernel('dev', true); 		
      self::$kernel->boot();
      self::$container = self::$kernel->getContainer();
    }

    public function get($id)
    {
      return self::$kernel->getContainer()->get($id);
    }
}

One of the important things to note is to ensure that the AppKernal.php is properly defined in terms of the location. Because I have this class in a sub-directory, it’s slightly off compared to the one in the article. I might eventually move it around but at the moment, this class’ purpose is to handle entity validation.

Now, let’s create a class that will inherit from it. The test class I will define will use the Product.php entity from my previous article. The main thing to realize here is that the product class will have $category as a many-to-one relationship. So in the Product.php class, I will use bring up the annotated version with the validations:

	
/**
 * 
 * @Assert\NotBlank()
 * @ORM\ManyToOne(targetEntity="category", inversedBy="products")
 * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
 */
 protected $category;

One thing that I added here compared to the previous definition was the @Assert\NotBlank annotation. What does this mean? Essentially, this makes it required for a user to select a category when saving a product. Without it, the validations will pass through, even though you may intend for that column not to be blank.

Another thing to note is that despite this field being an integer, we do not add more validations. Shouldn’t we add a regular expression to enforce that the data being saved here is also an integer? Actually, that isn’t necessary because the expected type is a Category object. So let’s set up a test class to show how that might work:

<?php 

// ProductTest.php 

namespace Ecommerce\ShoppingBundle\Tests\Entity; 
use Ecommerce\ShoppingBundle\Entity\Product; 
use Ecommerce\ShoppingBundle\Entity\Category; 
class ProductTest extends BaseEntityTest {   
  public function testValidator()  
  {     
    $v = $this->get('validator');
    $entity = new Product();
    $entity->setName('test product');
    $entity->setDescription('test description');		
    $errors = $v->validate($entity);
    $this->assertEquals(0, count($errors));
  }	
}

So what’s going on here? The first thing you should take note of is the line:

$v = $this->get('validator');

This line extracts the validation service that we set up in the base class. If we want to be even cooler we might even put this into another class that calls out to a method that handles this call, thus having us avoid any spelling mistakes. But this is just an example so we’ll keep it simple for now.

The next aspect to notice is that we instantiate the Product object and set a few methods with some data. When we run the phpunit on this class, we will get an error because we had declared that $category must not be blank. What happens if we try to set some data to it that looks valid such as:

$entity->setCategory(1);

This will still cause an error! Why? Isn’t the value on the category_id column a number after all? While that might be true, the validator is expecting through the mapping declaration a Category object. So to get this test to work, we could do something like:

$entity->setCategory(new Category());

Although this might look a little bogus because there’s no real data nor associated record, we have to remember that we only are testing the interfaces, not whether or not the  full system is working as expected. So in that sense, the validator will pass correctly.

(Visited 683 times, 1 visits today)

Comments

comments