Posted at
SymfonyDay 14

Symfony4のRepositoryをCodeceptionのDoctrine2モジュールを使ってUnitテストする

Symfony4のRepositoryのメソッドに対するテストで、公式の方法ではなく、Codeceptionを使ってテストする方法をご紹介します。


Codeceptionとは

CodeceptionはPHPのテスティングフレームワークです。以下のようなテストを一括で管理、実行できます。


  • Unitテスト

  • 機能テスト

  • フレームワークでの動作テスト

  • ブラウザでのテスト

  • APIのテスト

これらのテストを一元管理できるのが好きで、このCodeceptionをよく使っています。

詳しくはこちらのスライドをご覧ください。

https://speakerdeck.com/ippey/tesutotozhong-liang-kusurutamefalsecodeceptionfalsehazimekata-number-phpkansai?slide=36


サンプルプログラム

テストするためのサンプルプログラムを作っていきます。今回商品テーブルのデータを扱うProductRepository,Productエンティティを用意しました。Productエンティティは

name: 商品名

price: 価格
isPublished: 公開・非公開
createdAt: 登録日時
updatedAt : 更新日時

として、Repositoryに


src/Repository/ProductRepository.php

<?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ファイルを編集します。


tests/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


SymfonyDoctrine2Dbモジュールを追加します。今回はSQLiteを使いましたが、MySQLの場合はdsnとuser, passwordを設定します。編集後、このモジュールを利用できるように以下のコマンドを実行します。

vendor/bin/codecept build

これで、Codeceptionの準備は完了です。


テストを作る

まず、以下のコマンドでUnitTestのファイルを作成します。

vendor/bin/codecept g:test unit Repository/ProductRepository

これで、tests/unit/Repository/ProductRepositoryTest.phpが作成されるので、編集していきます。


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のスタブを作ったりでき、これを機能テストやブラウザテストでも利用することができます。ぜひ一度おためしください!!