Doctrine のリポジトリクラスをテストする方法

Symfony プロジェクトで Doctrine のリポジトリクラスをテストするのは、単純ではありません。 実際、リポジトリをロードするにはエンティティ、エンティティマネージャー、およびデータベース接続などいくつかのものをロードしたり準備しておく必要があります。

リポジトリクラスのテストには、次の 2 種類のアプローチがあります。

  1. ファンクショナルテスト: このアプローチでは、実際のデータベース接続を使い、データベースの実際のデータを使います。 ですのでセットアップして何かテストを行うことは簡単ですが、テストの実行速度が遅くなります。 ファンクショナルテスト も参照してください。
  2. Unit テスト: Unit テストは、より実行速度が速く、テストしたいことを正確に実行できます。 Unit テストを実行するには、このページで説明するように多少のセットアップが必要になります。 また、リポジトリクラスの Unit テストでは、たとえばクエリーのビルドといったメソッドのみをテストし、それらを実際に実行するメソッドのテストは行いません。

Unit テスト

Symfony と Doctrine では共通のテスティングフレームワークを使っていますので、Unit テストを Symfony プロジェクト内で記述するのはとても簡単です。 ORM には、Unit テストや必要なオブジェクトのモックを簡単に作成するためのツールが備わっています。 たとえば、接続オブジェクト(Connection)やエンティティマネージャーオブジェクト(EntityManager)のモッキングなどです。 Doctrine で提供されているテストコンポーネントを使い、基本的なセットアップを行うと、リポジトリクラスの Unit テストに Doctrine のツールを広く活用できます。

実際のクエリの実行をテストしたい場合は、ファンクショナルテスト(ファンクショナルテスト を参照)を行う必要があることに注意してください。 リポジトリクラスの Unit テストでは、クエリーをビルドするメソッドのみテストが可能です。

セットアップ

最初に、オートローダーの設定に DoctrineTests 名前空間を追加します。

// app/autoload.php
$loader->registerNamespaces(array(
    //...
    'Doctrine\\Tests'                => __DIR__.'/../vendor/doctrine/tests',
));

次に、Doctrine からエンティティやリポジトリを読み込めるように、エンティティマネージャーをセットアップします。

Doctrine は、デフォルトではエンティティのアノテーションメタデータを読み込めませんので、アノテーションリーダーの設定を行い、エンティティのパースと読み込みを行えるようにします。

// src/Acme/ProductBundle/Tests/Entity/ProductRepositoryTest.php
namespace Acme\ProductBundle\Tests\Entity;

use Doctrine\Tests\OrmTestCase;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\ORM\Mapping\Driver\DriverChain;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;

class ProductRepositoryTest extends OrmTestCase
{
    private $_em;

    protected function setUp()
    {
        $reader = new AnnotationReader();
        $reader->setIgnoreNotImportedAnnotations(true);
        $reader->setEnableParsePhpImports(true);

        $metadataDriver = new AnnotationDriver(
            $reader,
            // provide the namespace of the entities you want to tests
            'Acme\\ProductBundle\\Entity'
        );

        $this->_em = $this->_getTestEntityManager();

        $this->_em->getConfiguration()
            ->setMetadataDriverImpl($metadataDriver);

        // AcmeProductBundle:Product 形式を使えるようにする
        $this->_em->getConfiguration()->setEntityNamespaces(array(
            'AcmeProductBundle' => 'Acme\\ProductBundle\\Entity'
        ));
    }
}

上のコードを見ると、次のようなことに気づくでしょう:

  • テストクラスは、Unit テスト用の便利なメソッドが実装されている \Doctrine\Tests\OrmTestCase を継承します。
  • AnnotationReader をセットアップして、エンティティのパースと読み込みを行えるようにしています。
  • _getTestEntityManager メソッドを呼び出してエンティティマネージャーを取得しています。 このメソッドで取得できるのは、モック化された接続オブジェクトを使う、モック化されたエンティティマネージャーです。

セットアップはこれだけです。これで Doctrine のリポジトリクラスの Unit テストを記述する準備が整いました。

Unit テストの記述

Doctrine のリポジトリクラスのメソッドで、クエリーをビルドし、その場でクエリーを実行するのではなく、ビルドしたクエリーを返すようなメソッドのみ Unit テストが可能だということを思い出してください。 クエリーをビルドして返すメソッドとは、たとえば次のようなコードになります。

// src/Acme/StoreBundle/Entity/ProductRepository
namespace Acme\StoreBundle\Entity;

use Doctrine\ORM\EntityRepository;

class ProductRepository extends EntityRepository
{
    public function createSearchByNameQueryBuilder($name)
    {
        return $this->createQueryBuilder('p')
            ->where('p.name LIKE :name', $name)
    }
}

この例では、メソッドは QueryBuilder インスタンスを返しています。 このメソッドの結果は、さまざまな方法でテストできるでしょう。

class ProductRepositoryTest extends \Doctrine\Tests\OrmTestCase
{
    /* ... */

    public function testCreateSearchByNameQueryBuilder()
    {
        $queryBuilder = $this->_em->getRepository('AcmeProductBundle:Product')
            ->createSearchByNameQueryBuilder('foo');

        $this->assertEquals('p.name LIKE :name', (string) $queryBuilder->getDqlPart('where'));
        $this->assertEquals(array('name' => 'foo'), $queryBuilder->getParameters());
    }
 }

このテストコードでは、返された QueryBuilder オブジェクトを調べて、クエリーの各パーツが期待した内容になっていることを確認しています。 クエリービルダーで他に追加する可能性のある SQL パーツとしては、selectfromjoinsetgroupByhavingorderBy などがあります。

メソッドから返されるのが Query オブジェクトであったり、実際のクエリーのみをテストしたい場合には、次のようにして直接 DQL のクエリー文字列をテストすることもできます。

public function testCreateSearchByNameQueryBuilder()
{
    $queryBuilder = $this->_em->getRepository('AcmeProductBundle:Product')
        ->createSearchByNameQueryBuilder('foo');

    $query = $queryBuilder->getQuery();

    // DQL 文字列をテストする
    $this->assertEquals(
        'SELECT p FROM Acme\ProductBundle\Entity\Product p WHERE p.name LIKE :name',
        $query->getDql()
    );
}

ファンクショナルテスト

実際にクエリーを実行した結果をテストする必要がある場合は、Symfony のカーネルを boot して実際のデータベース接続を取得します。 この場合は、Unit テストの時とは異なり、テストクラスで WebTestCase を継承します。 WebTestCase を使うと、テストコード内で Symfony のカーネルの boot などを簡単に行えます。

// src/Acme/ProductBundle/Tests/Entity/ProductRepositoryFunctionalTest.php
namespace Acme\ProductBundle\Tests\Entity;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class ProductRepositoryFunctionalTest extends WebTestCase
{
    /**
     * @var \Doctrine\ORM\EntityManager
     */
    private $_em;

    public function setUp()
    {
            $kernel = static::createKernel();
            $kernel->boot();
        $this->_em = $kernel->getContainer()
            ->get('doctrine.orm.entity_manager');
    }

    public function testProductByCategoryName()
    {
        $results = $this->_em->getRepository('AcmeProductBundle:Product')
            ->searchProductsByNameQuery('foo')
            ->getResult();

        $this->assertEquals(count($results), 1);
    }
}