当使用链接到数据库的转换器时如何对 Symfony2 表单进行单元测试

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

TLDR:我是单元测试的新手,我有几个问题:

  1. 我的变压器测试写得好吗?
  2. 有没有办法将我的变压器测试与数据库分离?
  3. 如何使用数据库用转换器测试我的表单?
  4. 我应该将我的表格与我的变压器分离吗?

不知道是不是我的类太耦合了,是不是我的设计有问题,还是我对单元测试的理解不好。

这是一些背景。
我有一个带有不同小部件的表单对象。其中之一用于模型转换器。
此模型转换器使用与数据库的连接来检索正确的对象。

这是我的代码:

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';
    }

}

我使用 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()],
        ];
    }

}

由于我是单元测试的新手,我什至不知道这是否是测试该转换器的正确方法。
我开始为表单对象编写测试。我正在使用 TypeTestCase,但没有简单的方法来连接到数据库,而且我不能使用 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);
    }

}

最后一个测试失败了,因为转换器确实可以访问数据库,因为我正在将模拟传递给表单。所以我应该得到一个真实的对象(意味着类太耦合)还是我应该找到其他方法。

谢谢

这个方法很好,在最后一个方法中你必须模拟回购对象和回购响应。在示例中尝试此代码:

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);
    }