Laravel9でS3のファイル読み込みテストを書く
背景
LaravelでS3とのやり取りの処理を書く機会がありました.
テストの書き方について忘れてしまいそうだったので,メモとして残しておこうと思いまとめてみました.
環境
OS : MacOS(intel)
Laravel version : 9.14.1
PHP version : 8.1.6
サンプル
Laravel Sailでプロジェクトを作成・起動する
Laravelを簡単に動かせるLaravel Sailのプロジェクトを作成します.
$ curl -s "https://laravel.build/example-app?with=mysql,redis" | bash
1回パスワードを求められるので入力して完了です.
dockerコンテナを立ち上げていきます
$ cd example-app/
$ ./vendor/bin/sail up -d
http://localhost/ を開いてデフォルトページが表示されていれば,正しく起動できています.
# アプリケーションコンテナに入る
$ ./vendor/bin/sail bash
Xdebug: [Config] Invalid mode 'false' set for 'XDEBUG_MODE' environment variable, fall back to 'xdebug.mode' configuration setting (See: https://xdebug.org/docs/errors#CFG-C-ENVMODE)
PHP 8.1.6 (cli) (built: May 17 2022 16:46:54) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.6, Copyright (c) Zend Technologies
with Zend OPcache v8.1.6, Copyright (c), by Zend Technologies
with Xdebug v3.1.4, Copyright (c) 2002-2022, by Derick Rethans
# Laravelのバージョンを確認
$ php artisan --version
Xdebug: [Config] Invalid mode 'false' set for 'XDEBUG_MODE' environment variable, fall back to 'xdebug.mode' configuration setting (See: https://xdebug.org/docs/errors#CFG-C-ENVMODE)
Laravel Framework 9.14.1
$ exit
S3の準備
今回はテストを書くのが目的ですが,通常の動作確認もしておきたいと思います.
まず,S3バケットを作成します.
バケットの作成完了後,txtファイルを作成したバケットにアップロードします.中身は何でも良いのですが,今回は以下のようにしました.
今日は暑いですね.
もう5月も終わりです.1年の5/12が過ぎました.今年の残り7ヶ月はどのように過ごしますか?
2022.05.31
flysystem-aws-s3-v3をインストール
LaravelからS3とやり取りするのに必要なライブラリをインストールします.公式にも挙げられているflysystem-aws-s3-v3を入れていきます.
# アプリケーションコンテナに入る
$ ./vendor/bin/sail bash
$ composer require league/flysystem-aws-s3-v3 "^3.0"
$ exit
S3にアップロードしたtxtの中身を表示してみる
S3にアップロードした中身を取得して,viewで表示するだけの単純な処理を書いていきます.
...
// 追記
use App\Http\Controllers\S3TxtController;
...
// 追記
Route::get('/s3/txt', [S3TxtController::class, 'index']);
...
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\Storage;
class S3TxtController extends Controller
{
public function index()
{
$disk = Storage::disk('s3');
$data = $disk->get('s3_sample.txt');
if ($data === null) {
throw new \Exception('error');
}
return view('s3.index',
compact('data')
);
}
}
<p>{{$data}}</p>
すっ飛ばしましたが,S3へのアクセス権限を付与したIAMユーザーを作成しておく必要があります.作成したIAMユーザーのアクセスキーとシークレットアクセスキーは.envに設定します.
...
AWS_ACCESS_KEY_ID=[S3へのアクセス権限を付与したアクセスキー]
AWS_SECRET_ACCESS_KEY=[S3へのアクセス権限を付与したシークレットアクセスキー]
AWS_DEFAULT_REGION=[作成したバケットのリージョン名]
AWS_BUCKET=[作成したバケット名]
...
http://localhost/s3/txt にアクセスするとS3にアップロードしたs3_sample.txtの内容が表示されていることが確認できます.
テストを書く
いよいよ本題のテストを書いていきます.
S3へファイルを取得する部分は,txtの中身を変更したりしたいものです.テストではS3へ行かずモック化のようにしたいのが,今回のキモになります.
どうすれば良いのかというと,Storageファザードのfakeメソッドを用いればOKです.
use Illuminate\Support\Facades\Storage;
...
Storage::fake('s3');
のように書くことで,テストで$disk->get('s3_sample.txt');
したときにS3へファイルを取りに行くのではなく,ローカルのexample-app/storage/framework/testing/disks/s3/s3_sample.txt
を取得しに行くようにすることができます.
Storage.phpの$root = storage_path('framework/testing/disks/'.$disk);
で設定しているようです.なのでモック化しているわけではなく,取得先がローカルのテスト用ディレクトリに向く形になります.
...
public static function fake($disk = null, array $config = [])
{
$disk = $disk ?: static::$app['config']->get('filesystems.default');
$root = storage_path('framework/testing/disks/'.$disk);
if ($token = ParallelTesting::token()) {
$root = "{$root}_test_{$token}";
}
(new Filesystem)->cleanDirectory($root);
static::set($disk, $fake = static::createLocalDriver(array_merge($config, [
'root' => $root,
])));
return tap($fake)->buildTemporaryUrlsUsing(function ($path, $expiration) {
return URL::to($path.'?expiration='.$expiration->getTimestamp());
});
}
...
上記を用いてテストを書いてみます.
Storage::disk('s3')->put('s3_sample.txt', $data);
でテスト用のtxtを作成しておいて,$response = $this->get('/s3/txt');
でそのファイルを取得しに行っています.
<?php
namespace Tests\Feature;
use Tests\TestCase;
use Illuminate\Support\Facades\Storage;
class S3TxtTest extends TestCase
{
/**
* 正常系.
*/
public function testGeneral(): void
{
$data = 'テスト用Contentsです';
Storage::fake('s3');
Storage::disk('s3')->put('s3_sample.txt', $data);
$response = $this->get('/s3/txt');
$response->assertStatus(200)
->assertSee('Contents')
->assertViewIs('s3.index')
->assertViewHasAll([
'data' => $data,
]);
}
/**
* 異常系.
*/
public function testNoFileS3(): void
{
Storage::fake('s3');
$response = $this->get('/s3/txt');
$response->assertStatus(500);
}
}
fakeメソッドの場合,テスト用ディレクトリのファイルを全て削除してしまいます.persistentFakeメソッドを用いると消さないようなので,どこのテスト用ディレクトリを使っているかを知りたい時はpersistentFakeメソッドを使ってみると良さそうです.
テストを実行し,成功することを確認して完了です.
$ ./vendor/bin/sail test
Discussion