🎻

Symfony4+LiipTestFixturesBundleでyamlフィクスチャを使って機能テストを行う手順【完全版】

2020/03/15に公開

Symfony5もリリースされたというのにSymfony4の話ですみません😂

ただ、記事執筆時点で最新のLTSは4.4ですし、まだまだ当面はSymfony4で開発する人が多いかなと思います。

Symfonyのリリーススケジュールは こちら で確認できます。

なおかつ、Symfony4系でyamlフィクスチャを使って機能テストを行う方法について日本語で書かれた記事がパッと見たところなさそうだったので、ひとまず今回はSymfony4.4での具体的な手順をまとめておこうかなと思います👍

なお、この記事のサンプルコードは GitHubで公開しています ので、ぜひあわせてご参照ください。

1. Symfony4プロジェクトを作成

$ composer creat-project symfony/skeleton:"~4.4"

2. 依存ライブラリのインストールなど最低限の準備

機能テストを行うために、PHPUnit単体ではなく symfony/test-pack をインストールしましょう。

$ composer require --dev symfony/test-pack

Doctrine ORMを使いたいので、symfony/orm-pack をインストールしましょう。

$ composer require symfony/orm-pack

機能テストだけをする分には必須ではないですが、分かりやすさのために実際にブラウザでも動作確認をできるようにデータベースを準備しましょう。(今回はサンプルなのでsqliteを使います)

# .env
- DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7
+ #DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7
+ DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db
$ bin/console doctrine:database:create

3. エンティティとコントローラーを用意

<?php
// src/Controller/UserController.php
declare(strict_types=1);

namespace App\Controller;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Annotation\Route;

/**
 * @Route("/", name="user_")
 */
class UserController
{
    /**
     * @Route("/{id}/show", name="show")
     */
    public function show(int $id, EntityManagerInterface $em)
    {
        if (! $user = $em->find(User::class, $id)) {
            throw new NotFoundHttpException();
        }

        return new Response("Name: {$user->name}");
    }
}
<?php
// src/Entity/User.php
declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class User
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    public $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    public $name;
}

エンティティを作成したら、データベースを更新しましょう。

$ bin/console doctrine:migrations:diff
$ bin/console doctrine:migrations:migrate

4. ブラウザで動かしてみる

$ php -S localhost:8888 -t public

http://localhost:8888/1/show にアクセスして、404 Not Found( NotFoundHttpException )になれば正常に動いています。(データベースが空なので404で正解です✋)

5. 一旦機能テストを書いてみる

<?php
// tests/Controller/UserControllerTest.php
declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class UserControllerTest extends WebTestCase
{
    public function testShow(): void
    {
        $client = static::createClient();

        $crawler = $client->request('GET', '/1/show');
        $this->assertEquals(404, $client->getResponse()->getStatusCode());
    }
}
$ bin/phpunit
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

Testing Project Test Suite
2020-03-14T15:35:40+00:00 [error] Uncaught PHP Exception Symfony\Component\HttpKernel\Exception\NotFoundHttpException: "" at /Users/ttskch/ghq/github.com/ttskch/Symfony4-LiipTestFixtureBundle-FunctionalTest-Sample/src/Controller/UserController.php line 23
.                                                                   1 / 1 (100%)

Time: 352 ms, Memory: 20.00 MB

OK (1 test, 1 assertion)

期待どおり動いていますね👍

ちなみに、こちらのドキュメント で言及されているとおり、Symfonyで機能テストを行う際には、 KERNEL_CLASS 環境変数が設定されている必要があります。

今回の手順では、 symfony/test-pack をインストールしたときに自動生成された .env.test の中で定義されている ので特に意識していませんでしたが、もしかすると環境によっては KERNEL_CLASS 環境変数が定義されていませんよ的なエラーになるかもしれません。

その場合は今回のように .env.test を作るか、 phpunit.xml または phpunit.xml.dist

<env name="KERNEL_CLASS" value="App\Kernel" />

のように定義するなどして対応しましょう。

6. LiipTestFixtureBundleでyamlフィクスチャを使った機能テストを行う

いよいよ本題です。

まず大前提として、これまでSymfonyでyamlフィクスチャを使って機能テストをする際の定番バンドルだった LiipFunctionalTestBundle ですが、現在はフィクスチャをロードする機能の部分だけが LiipTestFixtureBundle という別バンドルに切り出されています。(Pull Requestは こちら

なので、Symfonyのデフォルトの WebTestCase クラスの機能に加えて単にyamlフィクスチャを使いたいだけであれば、LiipFunctionalTestBundleは入れずに、LiipTestFixtureBundleだけをインストールすればOKです👌

ただし、

との併用が必須となっているため、これらもあわせてインストールする必要があります。

これらを入れていない状態でフィクスチャをロードしようとすると、

  • BadMethodCallException: theofidry/alice-data-fixtures must be installed to use this method.
  • Error: Class 'Doctrine\Common\DataFixtures\ProxyReferenceRepository' not found

といったエラーで怒られます。

$ composer require --dev liip/test-fixtures-bundle theofidry/alice-data-fixtures doctrine/doctrine-fixtures-bundle

さて、これでyamlフィクスチャを使えるようになりました。

が、このままで機能テストを動かしてしまうと、 開発用のデータベースがyamlフィクスチャの内容で上書きされてしまいます。

というわけで、以下のような config/packages/test/doctrine.yaml ファイルを作成し、test環境ではテスト用のデータベースを使うように設定しておく必要があります。(LiipTestFixtureBundleのドキュメント にも説明がありますね)

# config/packages/test/doctrine.yaml
doctrine:
  dbal:
    driver: pdo_sqlite
    path: "%kernel.cache_dir%/test.db"
    url: null

url: null を書かないと、dev環境やprod環境で DATABASE_URL 環境変数を使ってデータベースを指定している場合には結局そちらが適用されてしまうので、要注意です。

ついでに、フィクスチャによって生成されたデータベースをキャッシュして2回目以降の機能テストが高速に実行されるように設定しておきましょう。

これについても LiipTestFixtureBundleのドキュメント に説明があります。

Symfony4の場合は、以下のように config/packages/test/framework.yaml に追記すればOKです。

# config/packages/test/framework.yaml
framework:
    test: true
    session:
        storage_id: session.storage.mock_file
+
+ liip_test_fixtures:
+     cache_db:
+         sqlite: liip_test_fixtures.services_database_backup.sqlite

さて、これで準備はすべて整いました。yamlフィクスチャを使った機能テストを書いて実行してみましょう。

まずはyamlフィクスチャを以下のような内容で書きます。

App\Entity\User:
  user1:
    id: 1
    name: Takashi

続いて、機能テストのコードに以下のように追記します。

<?php
declare(strict_types=1);

namespace App\Controller;

+ use Liip\TestFixturesBundle\Test\FixturesTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class UserControllerTest extends WebTestCase
{
+   use FixturesTrait;
+
+   protected function setUp(): void
+   {
+       $this->loadFixtureFiles([
+           __DIR__ . '/../../tests/fixtures/users.yaml',
+       ]);
+   }
+
    public function testShow(): void
    {
        $client = static::createClient();

        $crawler = $client->request('GET', '/1/show');
-       $this->assertEquals(404, $client->getResponse()->getStatusCode());
+       $this->assertEquals(200, $client->getResponse()->getStatusCode());
+
+       $this->assertEquals('Name: Takashi', $crawler->text());
    }
}

yamlフィクスチャにidが1の User エンティティを定義しているので、今度は /1/show が404ではなく200になり、さらにコントローラーの処理のとおり Name: Takashi とユーザーの名前が出力されるはずです。

実行してみると、

$ bin/phpunit
PHPUnit 7.5.20 by Sebastian Bergmann and contributors.

Testing Project Test Suite
.                                                                   1 / 1 (100%)

Time: 1.08 seconds, Memory: 22.00 MB

OK (1 test, 2 assertions)

無事、パスしましたね!🙌

おまけ:機能テストを実行するとTwigでdeprecation noticeが出る?

今回のサンプルでは使っていませんが、Symfony4.4でTwigBundleを導入している場合に、機能テストを実行すると以下のようなdeprecation noticeが出る場合があります。

2x: The "twig.exception_controller" configuration key has been deprecated in Symfony 4.4, set it to "null" and use "framework.error_controller" configuration key instead.
  2x in AdExpenseControllerTest::setUp from App\Import\Csv

一応テストは実行されますが、CLIのリターンコードは0でない値(エラー)になるので、そのままにしておくとCIが通らなくなってしまいます。

なので、Symfony4.4のマイグレーションガイドにも記載されているとおりconfig/packages/twig.yaml

twig:
    exception_controller: null

を設定する必要があります。

ちなみに、このTwigの例ではライブラリ側でdeprecationを解決する方法が提供されているので問題ないですが、もし他に使っている外部ライブラリのせいでDeprecation Noticeが出てCIが落ちてしまうような場合には、 SYMFONY_DEPRECATIONS_HELPER 環境変数を使ってdeprecation noticeをエラー扱いするかどうかの基準を細かく設定することができるので、覚えておくとよいかもしれません👍

詳細は こちらのドキュメント にありますが、例えば vendor 配下のdeprecation noticeはエラー扱いにしない という設定にするなら、 SYMFONY_DEPRECATIONS_HELPER 環境変数の値を max[self]=0 に設定すればよいです。

phpunit.xmlphpunit.xml.dist で設定するなら以下のような記述になります。

<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0" />

まとめ

  • yamlフィクスチャを使いたいだけなら LiipFunctionalTestBundle は必要なくて、 LiipTestFixtureBundle だけでOKになっています
  • ただし、依存ライブラリがいくつか必要で、具体的には以下の3つをインストールすると使えます
    • liip/test-fixtures-bundle
    • theofidry/alice-data-fixtures
    • doctrine/doctrine-fixtures-bundle
  • test環境では別のデータベースを使うようにする設定を忘れずに
  • test用データベースをキャッシュする設定も忘れずに
  • TwigBundleを導入しているSymfony4.4プロジェクトでは、機能テストを実行するとdeprecation noticeが出る場合があるので、適切に対応しましょう
    • SYMFONY_DEPRECATIONS_HELPER 環境変数を使えばdeprecation noticeをエラー扱いにするかどうかの細かい設定ができるので、ついでに覚えておくといいかもしれません
GitHubで編集を提案

Discussion