PHPUnit
laravel
laravel5.5

Laravel5.5 Seederで単体テストのテストデータを登録してみる

More than 1 year has passed since last update.

概要

Laravel5.2の時にやってたテストデータの登録方法が上手くいかず、少しハマったのでそのまとめ。

環境

Laravel 5.5.9
PHP 7.1.3
MySQL 5.7.19

Laravel5.2の時の方法

確かこんな感じで、@beforeアノテーションでテスト前にSeederを実行してました。

tests\Unit\ExampleTest.php
<?php

namespace Tests\Unit;

use Illuminate\Database\Seeder;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class ExampleTest extends TestCase
{
    use DatabaseMigrations;

    /**
     * @before
     */
    public function setUpFixtures()
    {
        $this->seed(__CLASS__ . 'Seeder');
    }

    /**
     * @test
     */
    public function サンプルテスト()
    {
        $this->markTestIncomplete('テストを実装する!');
    }
}

/**
 * テストデータ登録Seeder
 */
class ExampleTestSeeder extends Seeder
{
    public function run()
    {
        // @todo テストデータ登録
    }
}

Laravel5.5でも同様にやってみる

RefreshDatabaseトレイトが追加されてるので、ついでに変更。

tests\Unit\ExampleTest.php
<?php

namespace Tests\Unit;

use Illuminate\Database\Seeder;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @before
     */
    public function setUpFixtures()
    {
        $this->seed(__CLASS__ . 'Seeder');
    }

    /**
     * @test
     */
    public function サンプルテスト()
    {
        $this->markTestIncomplete('テストを実装する!');
    }
}

/**
 * テストデータ登録Seeder
 */
class ExampleTestSeeder extends Seeder
{
    public function run()
    {
        // @todo テストデータ登録
    }
}

結果

エラーが発生。
どうもアプリケーションクラスの初期化が終わってないっぽい。
アプリケーションの初期化の方法が変わったのかな?(後で調べて追記するかも)

1) Tests\Unit\ExampleTest::サンプルテスト
Error: Call to a member function call() on null

(追記:2017-09-22)
PHPUnit側の処理が変わったみたい。(PHPUnit\Util\Test::getHookMethodsメソッド)
(PHPUnitのバージョンによって挙動が変わるなら、サンプルソースも問題なので修正しました)


原因

ソースをみた感じだと、setUpメソッド内でアプリケーションの初期化を行なっているが、PHPUnit的にはsetUpより@beforeアノテーションで定義したものが先に呼ばれる模様。
array_shift array_unshiftで、見つかった@beforeのメソッドを格納してるので、後に見つかったものが先に呼ばれるっぽい。そしてデフォルト値にsetUpが入っている。)

 Laravel5.5で動くよう修正

afterApplicationCreatedメソッドでアプリケーションの初期化完了時のコールバックを定義できるみたい。

tests\Unit\ExampleTest.php
<?php

namespace Tests\Unit;

use Illuminate\Database\Seeder;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    /**
     * @before
     */
    public function setUpFixtures()
    {
        // 下のように書き換え
        //$this->seed(__CLASS__ . 'Seeder');

        // 訂正(2017-09-22)
        //$this->afterApplicationCreated(function() {
        //    $this->seed(__CLASS__ . 'Seeder');
        //});
        if ($this->setUpHasRun) {
            // アプリケーションのセットアップが完了しているならそのまま実行
            $this->seed(__CLASS__ . 'Seeder');
        } else {
            // アプリケーションのセットアップが完了していないならコールバックで設定
            $this->afterApplicationCreated(function() {
                $this->seed(__CLASS__ . 'Seeder');
            });
        }
    }

    /**
     * @test
     */
    public function サンプルテスト()
    {
        $this->markTestIncomplete('テストを実装する!');
    }
}

/**
 * テストデータ登録Seeder
 */
class ExampleTestSeeder extends Seeder
{
    public function run()
    {
        // @todo テストデータ登録
    }
}

再度実行して動くことを確認。

$ vendor/bin/phpunit 
PHPUnit 6.3.0 by Sebastian Bergmann and contributors.

I                                                                   1 / 1 (100%)

Time: 580 ms, Memory: 12.00MB

OK, but incomplete, skipped, or risky tests!
Tests: 1, Assertions: 0, Incomplete: 1.

まとめ

setUp() > @before > テスト実行 の順だと思い込んでた...
(2017-09-22)
追記しましたが、PHPUnitのバージョンで挙動が変わるっぽい。
ちゃんと確認してよかった。。サンプルソースも修正しました。

内容に間違いがあるとか、もっと良い方法があるとか、何かあればご指摘ください。

以上です。