Symfony4のRepositoryのメソッドに対するテストで、公式の方法ではなく、Codeceptionを使ってテストする方法をご紹介します。
Codeceptionとは
CodeceptionはPHPのテスティングフレームワークです。以下のようなテストを一括で管理、実行できます。
- Unitテスト
- 機能テスト
- フレームワークでの動作テスト
- ブラウザでのテスト
- APIのテスト
これらのテストを一元管理できるのが好きで、このCodeceptionをよく使っています。
詳しくはこちらのスライドをご覧ください。
サンプルプログラム
テストするためのサンプルプログラムを作っていきます。今回商品テーブルのデータを扱うProductRepository,Productエンティティを用意しました。Productエンティティは
name: 商品名
price: 価格
isPublished: 公開・非公開
createdAt: 登録日時
updatedAt : 更新日時
として、Repositoryに
<?php
namespace App\Repository;
use App\Entity\Product;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
/**
* @method Product|null find($id, $lockMode = null, $lockVersion = null)
* @method Product|null findOneBy(array $criteria, array $orderBy = null)
* @method Product[] findAll()
* @method Product[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ProductRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Product::class);
}
/**
* 公開フラグで検索
*
* @param $isPublished
* @return mixed
*/
public function findByIsPublished($isPublished)
{
return $this->createQueryBuilder('p')
->where('p.isPublished = :isPublished')
->setParameter(':isPublished', $isPublished)
->orderBy('p.id', 'ASC')
->getQuery()
->getResult();
}
}
公開・非公開のフラグの値で検索するメソッドを作成しました。このメソッドをCodeceptionでUnitテストしてみます。
(データベース・テーブル作成については割愛します。)
Codeceptionの準備
インストール
まずはインストールです。composerでインストールします。
cd your-project
composer require codeception/codeception
# わーっとインストールが進んだ後
Symfony operations: 2 recipes (c93d57139f97ec473429f36a0ba32009)
- Configuring phpunit/phpunit (>=4.7): From github.com/symfony/recipes:master
- WARNING codeception/codeception (>=2.3): From github.com/symfony/recipes-contrib:master
The recipe for this package comes from the "contrib" repository, which is open to community contributions.
Review the recipe at https://github.com/symfony/recipes-contrib/tree/master/codeception/codeception/2.3
Do you want to execute this recipe?
[y] Yes
[n] No
[a] Yes for all packages, only for the current installation session
[p] Yes permanently, never ask again for this project
(defaults to n): y # ここでyを選択
設定
unit.suite.ymlファイルを編集します。
# Codeception Test Suite Configuration
#
# Suite for unit or integration tests.
actor: UnitTester
modules:
enabled:
- Asserts
- \App\Tests\Helper\Unit
- Symfony:
app_path: 'src'
environment: 'dev'
- Doctrine2:
depends: Symfony
- Db:
dsn: "sqlite:var/data.db"
user: ""
password: ""
populate: false
Symfony、Doctrine2、Dbモジュールを追加します。今回はSQLiteを使いましたが、MySQLの場合はdsnとuser, passwordを設定します。編集後、このモジュールを利用できるように以下のコマンドを実行します。
vendor/bin/codecept build
これで、Codeceptionの準備は完了です。
テストを作る
まず、以下のコマンドでUnitTestのファイルを作成します。
vendor/bin/codecept g:test unit Repository/ProductRepository
これで、tests/unit/Repository/ProductRepositoryTest.phpが作成されるので、編集していきます。
<?php namespace App\Tests\Repository;
use App\Entity\Product;
use Codeception\Module\Doctrine2;
class ProductRepositoryTest extends \Codeception\Test\Unit
{
/**
* @var \App\Tests\UnitTester
*/
protected $tester;
protected function _before()
{
}
protected function _after()
{
}
// tests
/**
* @throws \Codeception\Exception\ModuleException
*/
public function testSomeFeature()
{
$this->createTestData();
/** @var Doctrine2 $module */
$module = $this->getModule('Doctrine2');
$repository = $module->em->getRepository(Product::class);
$rows = $repository->findByIsPublished(true);
$this->assertCount(2, $rows);
$rows = $repository->findByIsPublished(false);
$this->assertCount(1, $rows);
}
/**
* @throws \Codeception\Exception\ModuleException
*/
protected function createTestData()
{
/** @var Doctrine2 $module */
$module = $this->getModule('Doctrine2');
$now = new \DateTime();
$module->haveInRepository(Product::class, [
'name' => '商品1',
'price' => 100,
'isPublished' => true,
'createdAt' => $now,
'updatedAt' => $now,
]);
$module->haveInRepository(Product::class, [
'name' => '商品2',
'price' => 200,
'isPublished' => false,
'createdAt' => $now,
'updatedAt' => $now,
]);
$module->haveInRepository(Product::class, [
'name' => '商品3',
'price' => 300,
'isPublished' => true,
'createdAt' => $now,
'updatedAt' => $now,
]);
}
}
$this->getModule('Doctrine2');
でDoctrine2モジュールを取得します。
$module->haveInRepository('クラス名', 'データ配列');
で、テストデータを作成します。なお、毎テスト時にトランザクションを開始し、テスト後にロールバックしているのでここで作成したテストデータは保存されることはありません。
$module->em
は、EntityManagerです。これを利用してProductRepositoryインスタンスを取得します。あとは、インスタンスからメソッドを実行し、想定されたデータが取得できているかテストします。
備考
ほんとうは、 haveInRepositoryなどのDoctrine2のメソッドは $this->tester->haveInRepository();
などのように$this->testerから実行できるたんですが、Codeception, Symfonyのバージョンば上がったためか、実行できなくなってました。このあたり、設定が悪いのかもしれないので、設定方法などご存知の方は教えていただきたいです。🙏
まとめ
今回はデータ取得系のテストでしたが、Doctrine2モジュールにはこの他にもデータが存在するかテストしたり、Repositoryのスタブを作ったりでき、これを機能テストやブラウザテストでも利用することができます。ぜひ一度おためしください!!