Outils pour utilisateurs

Outils du site


pele_mele:stack_exchange:stackoverflow-27801219

How to unit test a Symfony2 form when it uses a transformer linked to a database

TLDR: I am new to unit tests and I have few questions:

  1. Are my transformer tests well written?
  2. Is there a way to decoupled my transformer tests from the database?
  3. How to test my form with the transformer using the database?
  4. Should I decouple my form from my transformer?

I don't know if my classes are too coupled, if my design is flawed or if my understanding of the unit tests is bad.

Here is some background. I have a form object with different widgets. One of them is used within a model transformer. This model transformer uses a connection to the database to retrieve the proper object.

Here is my code:

class BookToStringTransformer implements DataTransformerInterface {
    private $om;
 
    public function __construct(ObjectManager $om) {
        $this->om = $om;
    }
 
    public function transform($book) {
        if (!$book instanceof Book) {
            return "";
        }
 
        return $book->getName();
    }
 
    public function reverseTransform($string) {
        if (!is_string($string) || !$string) {
            return null;
        }
 
        $book = $this->om
                ->getRepository('MyBundle:Book')
                ->findOneBy(array('name' => $string))
        ;
 
        if (null === $book) {
            throw new TransformationFailedException(sprintf(
                    'The book "%s" does not exist!', $string
            ));
        }
 
        return $book;
    }
}
 
class ItemType extends AbstractType {
    private $om;
 
    public function __construct(ObjectManager $om) {
        $this->om = $om;
    }
 
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $bookTransformer = new BookToStringTransformer($this->om);
        $builder->add($builder->create('book', 'text', array(
                    'required' => false,
                ))->addModelTransformer($bookTransformer));
    }
 
    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults(array(
            'data_class' => 'MyBundle\Entity\Item',
        ));
    }
 
    public function getName() {
        return 'mybundle_item';
    }
}

I wrote unit tests for the transformer using the KernelTestCase

class BookToStringTransformerTest extends KernelTestCase {
    private $name = 'existing name';
    private $em;
 
    public function setUp() {
        static::$kernel = static::createKernel();
        static::$kernel->boot();
        $this->em = static::$kernel->getContainer()
                ->get('doctrine')
                ->getManager();
    }
 
    public function testReverseTransform_whenNameExists_returnsBookObject() {
        $transformer = new BookToStringTransformer($this->em);
        $book = $transformer->reverseTransform($this->name);
        $this->assertInstanceOf('MyBundle\Entity\Book', $book, 'Should return a Book object');
        $this->assertEquals($this->name, $book->getName(), 'Should return a Book object with the selected name');
    }
 
    /**
     * @expectedException Symfony\Component\Form\Exception\TransformationFailedException
     */
    public function testReverseTransform_whenNameDoesNotExist_throwsException() {
        $transformer = new BookToStringTransformer($this->em);
        $transformer->reverseTransform('unknown name');
    }
 
    /**
     * @param mixed $invalid_parameter
     * @dataProvider provideInvalidParameter
     */
    public function testReverseTransform_whenParameterIsInvalid_returnsNull($invalid_parameter) {
        $om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
        $transformer = new BookToStringTransformer($om);
        $this->assertNull($transformer->reverseTransform($invalid_parameter), 'Should return a NULL value');
    }
 
    /**
     * @return array
     */
    public function provideInvalidParameter() {
        return [
            [null],
            [false],
            [true],
            [''],
            [[]],
            [new \stdClass()],
        ];
    }
 
    public function testTransform_whenParameterIsBookObject_returnsName() {
        $book = $this->em->getRepository('MyBundle:Book')
                ->findOneBy(array('name' => $this->name));
        $om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
        $transformer = new BookToStringTransformer($om);
        $this->assertEquals($this->name, $transformer->transform($book), 'Should return a string containing the name');
    }
 
    /**
     * @param mixed $not_book
     * @dataProvider provideInvalidBookObject
     */
    public function testTransform_whenParameterIsNotBookObject_returnsEmptyString($not_book) {
        $om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
        $transformer = new BookToStringTransformer($om);
        $this->assertEquals("", $transformer->transform($not_book), 'Should return an empty string to be chained');
    }
 
    /**
     * @return array
     */
    public function provideInvalidBookObject() {
        return [
            [null],
            [123],
            ['123'],
            [[]],
            [true],
            [new \stdClass()],
        ];
    }
}

As I am new to unit tests, I don't even know if it is the proper way to test that transformer. I start writing tests for the form object. I am using the TypeTestCase but there is no simple way to get the connection to the database and I can't use the KernelTestCase.

class ItemTypeTest extends TypeTestCase {
    /**
     * @expectedException \PHPUnit_Framework_Error
     */
    public function test_whenCreatedWithNoParameters_raiseException() {
        new ItemType();
    }
 
    /**
     * @expectedException \PHPUnit_Framework_Error
     */
    public function test_whenCreatedWithBadParameters_raiseException() {
        new ItemType(123);
    }
 
    public function test_whenCreatedWithGoodParameters_createsFormObject() {
        $om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
        $type = new ItemType($om);
        $form = $this->factory->create($type);
        $this->assertInstanceOf('Symfony\Component\Form\Form', $form);
    }
 
    public function test_whenSubmittedWithGoodData() {
        $formData = array(
            'name' => 'existing name',
        );
 
        $om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
        $type = new ItemType($om);
        $form = $this->factory->create($type);
 
        $form->submit($formData);
    }
}

The last test fails because the transformer does get access to the database since I am passing a mock to the form. So should I get a real object (meaning classes are too coupled) or should I find an other way.

Thank you

The approach is good, in the last method you must mock the repo object and the repo response. In example try this code:

public function test_whenSubmittedWithGoodData() {
    $formData = array(
        'name' => 'existing name',
    );
 
    $om = $this->getMockBuilder('Doctrine\Common\Persistence\ObjectManager')->getMock();
 
    $repoMock= $this->getMock('Doctrine\ORM\EntityRepository', array(), array(), '', false);
 
    $om
        ->expects($this->atLeastOnce())
        ->method('getRepository')
        ->withAnyParameters()
        ->will($this->returnValue($repoMock));
 
 
    $repoMock
        ->expects($this->atLeastOnce())
        ->method('findOneBy')
        ->withAnyParameters()
        ->will($this->returnValue($mockedBook));
 
    $type = new ItemType($om);
    $form = $this->factory->create($type);
 
    $form->submit($formData);
}
pele_mele/stack_exchange/stackoverflow-27801219.txt · Dernière modification : 2020/11/18 03:42 de alexis