2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Laravel #2Advent Calendar 2018

Day 7

parent::tearDown()後にAliasやFacadeが使えなくなる原因を追う

Last updated at Posted at 2018-12-07

環境

  • Laravel 5.6
  • composer install した直後の状態

tearDown()

Laravelでテストコードを書く際、テスト後の後処理をtearDown()に書くと思います。
例えば、テスト用に設定したキャッシュを削除するため、Cacheファサードのforget()メソッドを呼び出したいとします。

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Support\Facades\Cache;

class ExampleTest extends TestCase
{
    protected function tearDown()
    {
        parent::tearDown(); // TODO: Change the autogenerated stub

        Cache::forget('hoge'); // テスト用に設置したキャッシュを削除したい
    }
}


しかし、上記コードは動きません。
Cacheクラスは登録されていない、という類のエラーが出ます。

どうしてCacheが動かなくなるのかを追っていきます。

parent::tearDown()

parent::tearDown()は何をやっているのでしょうか。
呼び出されるのは下記のコードです。

    /**
     * Clean up the testing environment before the next test.
     *
     * @return void
     */
    protected function tearDown()
    {
        if ($this->app) {
            foreach ($this->beforeApplicationDestroyedCallbacks as $callback) {
                call_user_func($callback);
            }

            $this->app->flush();

            $this->app = null;
        }

        $this->setUpHasRun = false;

        if (property_exists($this, 'serverVariables')) {
            $this->serverVariables = [];
        }

        if (property_exists($this, 'defaultHeaders')) {
            $this->defaultHeaders = [];
        }

        if (class_exists('Mockery')) {
            if ($container = Mockery::getContainer()) {
                $this->addToAssertionCount($container->mockery_getExpectationCount());
            }

            Mockery::close();
        }

        if (class_exists(Carbon::class)) {
            Carbon::setTestNow();
        }

        $this->afterApplicationCreatedCallbacks = [];
        $this->beforeApplicationDestroyedCallbacks = [];

        Artisan::forgetBootstrappers();
    }

ここで注目するのは、 $this->app->flush(); です。
$this->flush()で呼び出される処理は下記のコードです。

$this->flush()

    /**
     * Flush the container of all bindings and resolved instances.
     *
     * @return void
     */
    public function flush()
    {
        parent::flush();

        $this->buildStack = [];
        $this->loadedProviders = [];
        $this->bootedCallbacks = [];
        $this->bootingCallbacks = [];
        $this->deferredServices = [];
        $this->reboundCallbacks = [];
        $this->serviceProviders = [];
        $this->resolvingCallbacks = [];
        $this->afterResolvingCallbacks = [];
        $this->globalResolvingCallbacks = [];
    }

    /**
     * Flush the container of all bindings and resolved instances.
     *
     * @return void
     */
    public function flush()
    {
        $this->aliases = [];
        $this->resolved = [];
        $this->bindings = [];
        $this->instances = [];
        $this->abstractAliases = [];
    }

上記のコードを読んでもらえば察しが付くかと思いますが、$this->flush()はAliasやFacadeの登録情報をすべて空配列に置き換えています。
つまり、起動時に登録された情報が無くなってしまいます。

そのため、parent::tearDown() を読んだ後ではCacheファサードが使えなくなっていたんですね。

どう対応すべきか

parent::tearDown()後にファサードが動かない場合、どうすべきか。
私が思いつくのは下記の通りです。

  1. 各Example最後に削除処理を書く
  2. parent::tearDown()前に削除処理を書く
  3. 外部にデータを置かずモックを使ってテスト内で完結させる
  4. 頑張ってもう一度登録情報を設定し直す

一番手頃なのは2番ですが、テストであるという事を考えると3番でやっていきたいところ。
ただし、ファサードのモックはFeatureテストだと上手くいかないケースもあり…。
特にテストの在り方にこだわりが無ければ2番で実装するのが良いかと思います。

2
0
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?