Edited at

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


環境


  • 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();
}

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Testing/TestCase.php#L130-L173

ここで注目するのは、$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 = [];
}

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Foundation/Application.php#L1121-L1140

    /**

* Flush the container of all bindings and resolved instances.
*
* @return void
*/

public function flush()
{
$this->aliases = [];
$this->resolved = [];
$this->bindings = [];
$this->instances = [];
$this->abstractAliases = [];
}

https://github.com/laravel/framework/blob/5.7/src/Illuminate/Container/Container.php#L1164-L1176

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

つまり、起動時に登録された情報が無くなってしまいます。

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


どう対応すべきか

parent::tearDown()後にファサードが動かない場合、どうすべきか。

私が思いつくのは下記の通りです。


  1. 各Example最後に削除処理を書く

  2. parent::tearDown()前に削除処理を書く

  3. 外部にデータを置かずモックを使ってテスト内で完結させる

  4. 頑張ってもう一度登録情報を設定し直す

一番手頃なのは2番ですが、テストであるという事を考えると3番でやっていきたいところ。

ただし、ファサードのモックはFeatureテストだと上手くいかないケースもあり…。

特にテストの在り方にこだわりが無ければ2番で実装するのが良いかと思います。