前書き
今回は作成中のポートフォリオの処理にミスがないか、確認するためのテストの作成を行いました。
環境
上記記事に準じて環境構築を行っています、
実現したいこと
・各コントローラーの処理のテストをしたい。
→リソースコントローラにあたる7つのメソッド(index,edit,create,show,store,delete,update)と画像登録のテスト
対応方法
①事前設定
・新しくテストを作成します
php artisan make:test テスト名
--unitコマンドをつけると、tests/Unitディレクトリに、
つけないとtests/Featureディレクトリにテストが作成されます。
Featureテストは、小さい単位(今回は各コントローラのメソッド)が対象になります。
Unitテストは、アプリケーションを利用しないため、Eloquentのメソッドが使用できない等の制約がありますが、テストの実行が軽いです。
モデル名を使ってテストを作成すると自動で紐付けされましたが、そうでない場合は、テスト内にuse モデル名を記載し連携させる必要があります。
今回はFeatureテストを作成していきます。
②テスト用DBの作成
今回はdockerで環境構築しているので、テスト用DBコンテナの作成を実施します。下記記事を参照させていただきました。とても分かり易かったです、
db-test:
build: ./infra/mysql-test
volumes:
- db-store:/db-test/var/lib/mysql
ports:
- 3000:3306
※インデントがずれるとエラーが出るので注意
FROM mysql/mysql-server:8.0
ENV MYSQL_DATABASE=laravel_local_test \
MYSQL_USER=phper \
MYSQL_PASSWORD=secret \
MYSQL_ROOT_PASSWORD=secret \
TZ=Asia/Tokyo
元記事では設定ファイル(.my.cnf)が入っていましたが、今回は省略しています。
.env.testingファイル(テスト用の環境変数を記載)を作成します。
phpunit.xmlの内容に合うよう、既存の.env.exampleファイルをコピーして編集します。
APP_ENV=testing
APP_KEY=
DB_CONNECTION=mysql
DB_HOST=db-test
DB_PORT=3306
DB_DATABASE=laravel_local_test
DB_USERNAME=phper
DB_PASSWORD=secret
php artisan key:generate --env=testing
テスト用DBをmigrateします。
docker compose exec app php artisan migrate --env=testing
--env=testingコマンドをつけることで、.env.testingの環境変数を参照してDBを作成します。
③テストコードの作成
今回は例としてArticleControllerを対象にします。
Factoryの記載は割愛します。
public function up()
{
Schema::create('articles', function (Blueprint $table) {
$table->bigIncrements('id');
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->foreignId('radio_id')->constrained('radios')->onDelete('cascade');
$table->date('radio_date');
$table->text('body');
$table->text('link')->nullable();
$table->timestamps();
});
}
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;
use App\Models\Article;
use App\Models\User;
class ArticleTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
use RefreshDatabase;
public function setUp(): void
{
parent::setUp();
$this->article = Article::factory()->create();
$this->user = User::factory()->create();
$this->data = ([
'user_id' => $this->article->user_id,
'radio_id' => $this->article->radio_id,
'radio_date' => $this->article->radio_date,
'body' => $this->article->body,
'link' => $this->article->link,
]);
}
//index
public function test_index()
{
$response = $this->actingAs($this->user)
->get('/articles');
$response->assertStatus(200);
}
//create
public function test_create()
{
$response = $this->actingAs($this->user)
->get('/articles/create');
$response->assertStatus(200)
->assertViewHas('radios');
}
//store
public function test_store()
{
$response = $this->actingAs($this->user)
->post('/articles', $this->data);
$response->assertRedirect('/articles');
$this->assertDatabaseHas('articles', [
'user_id' => $this->article->user_id,
'radio_id' => $this->article->radio_id,
'radio_date' => $this->article->radio_date,
'body' => $this->article->body,
'link' => $this->article->link,
]);
}
//edit
public function test_edit()
{
$response = $this->actingAs($this->article->user)
->from('/articles/' . $this->article->id)
->get('/articles/' . $this->article->id . '/edit');
$response->assertStatus(200)
->assertViewHas('radios');
}
//update
public function test_update()
{
$response = $this->actingAs($this->article->user)
->from('/articles/' . $this->article->id . '/edit')
->put(
'/articles/' . $this->article->id,
[
'user_id' => $this->article->user_id,
'radio_id' => $this->article->radio_id,
'radio_date' => $this->article->radio_date,
'body' => 'aaa',
]
);
$response->assertRedirect('/articles');
$this->assertDatabaseHas('articles', [
'body' => 'aaa',
]);
}
//destroy
public function test_destroy()
{
$response = $this->actingAs($this->article->user)
->delete('/articles/' . $this->article->id);
$response->assertRedirect('/articles');
$this->assertDatabaseMissing('articles', $this->data)
->assertEquals(0, Article::count());
}
}
use RefreshDatabase;
テストを実行する度に、migrate:refreshされます。
$this->actingAs(___)
actingAs内に取る引数は、
登録済みユーザーは、$this->user、
投稿者のみ権限がある場合は、$this->article->user
画像投稿
public function test_image_store()
{
$image = HttpUploadedFile::fake()
->image('hoge.png');
$response = $this->actingAs($this->user)
->post('/articles', [
'image' => $image,
]);
$response->assertRedirect('/articles');
$this->assertDatabaseHas('articles', [
'image' => 'hoge.png',
]);
}
public function test_image_update()
{
$image = HttpUploadedFile::fake()
->image('hogehoge.png');
$response = $this->actingAs($this->user)
->from('/articles/' . $this->article->id)
->put('/articles/' . $this->article->id, [
'image' => $image,
]);
$response->assertRedirect('/articles/' . $this->article->id);
$this->assertDatabaseHas('articles', [
'image' => 'hogehoge.png',
]);
}
$image = HttpUploadedFile::fake()
テスト用のダミー画像を作成します。
④テスト実行
docker compose exec app php artisan test
もしくは
./vendor/bin/phpunit
最後に
より良いテストコードの記述方法や投稿内容に誤りがありましたら、ご指摘いただけると助かります。
今後も学習内容の備忘録として投稿していきたいと思います。
また、自動テスト(CI/CD)についても調べて、今後実装していきたいです。
参考