パターン1
Unitテスト
src/tests/Unit/Repositories/SearchTargetRepositoryTest.php
<?php
namespace Tests\Unit\Repositories;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Oms\Models\Author;
use Oms\Models\Post;
use Oms\UseCase\Front\Search\FrontSearchRequest;
use Oms\Models\SearchTarget;
use Oms\Repositories\Interfaces\SearchTargetRepositoryInterface;
use Oms\Repositories\SearchTargetRepository;
class SearchTargetRepositoryTest extends TestCase
{
use RefreshDatabase;
/**
* @test
*/
public function bind()
{
// バインディングテスト
$repository = app(SearchTargetRepositoryInterface::class);
$this->assertThat($repository, $this->isInstanceOf(SearchTargetRepository::class));
}
/**
* @test
*/
public function searchAuthor()
{
/** @var SearchTargetRepository $repository */
$repository = app(SearchTargetRepository::class);
// 著者データと中間テーブルの必須データ生成
$authors[] = factory(Author::class)->create([
'name' => 'Aさん',
'name_ruby' => 'えーさん',
]);
$authorIdsWithSort[] = [
$authors[0]->id => ['sort' => 1],
];
$authors[] = factory(Author::class)->create([
'name' => 'Bさん',
'name_ruby' => 'びーさん',
]);
$authorIdsWithSort[] = [
$authors[1]->id => ['sort' => 1],
];
$authors[] = factory(Author::class)->create([
'name' => 'Cさん',
'name_ruby' => 'しーさん',
]);
$authorIdsWithSort[] = [
$authors[2]->id => ['sort' => 1],
];
// 公開記事データ生成(著者データと同じ数)
$posts = factory(Post::class, count($authors))->create();
$search_targets = factory(SearchTarget::class);
foreach ($posts as $index => $post) {
$post->authors()->syncWithoutDetaching($authorIdsWithSort[$index]);
// 検索対象の記事データ生成
$search_targets->create(
[
'targetable_id' => $post->id,
'targetable_type' => 'Oms\Models\Post',
]
);
}
// 部分一致で2件ヒット
$actual = $repository->search((new FrontSearchRequest())->query('Aさ しー')->build())->get();
$this->assertCount(2, $actual);
}
}
テスト対象の処理
<?php
declare(strict_types=1);
namespace Oms\Repositories;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Oms\Models\SearchTarget;
use Oms\Repositories\Interfaces\SearchTargetRepositoryInterface;
use Oms\UseCase\Front\Search\FrontSearchRequest;
use Oms\Models\Post;
use Oms\Models\Author;
/**
* Class SearchTargetRepository
* @package Oms\Repositories
*/
class SearchTargetRepository implements SearchTargetRepositoryInterface
{
/** @var SearchTarget */
protected $orm;
/**
* @inheritdoc
*/
public function __construct(SearchTarget $searchTarget)
{
$this->orm = $searchTarget;
}
/**
* @param FrontSearchRequest $request
*
* @return Builder
*/
public function search(FrontSearchRequest $request): Builder
{
$builder = $this->orm->newQuery()->with([
'targetable',
'targetable.medium',
]);
if ($request->getCategory() === 1) {
// 1: ニュース, 2: コラム
$builder->whereIn('category', [1, 2]);
} elseif ($request->getCategory() !== 0) {
$builder->where('category', $request->getCategory());
}
$keywords = $request->getKeywords();
// 著者(氏名・ふりがな)を条件にpost_idを取得
$postAuthorsBuilder = Author::query()->join('author_post', function (JoinClause $join) {
$join->on('authors.id', '=', 'author_post.author_id');
});
$postAuthorsBuilder->where(function (Builder $innerBuilder) use ($keywords) {
foreach ($keywords as $keyword) {
$innerBuilder->orWhere(function (Builder $orBuilder) use ($keyword) {
$orBuilder->orWhereRaw('authors.name COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
$orBuilder->orWhereRaw('authors.name_ruby COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
});
}
});
$author_post_ids = $postAuthorsBuilder->pluck('post_id');
foreach ($keywords as $keyword) {
// titleとtextはor検索にしたいのでサブクエリとする
$builder->where(function (Builder $innerBuilder) use ($keyword, $author_post_ids) {
$innerBuilder->orWhereRaw('title COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
$innerBuilder->orWhereRaw('text COLLATE utf8mb4_unicode_ci LIKE ?', ["%$keyword%"]);
// 著者もor検索
$innerBuilder->orWhere(function (Builder $innerSubBuilder) use ($author_post_ids) {
$innerSubBuilder->whereIn('targetable_id', $author_post_ids);
$innerSubBuilder->where('targetable_type', Post::class);
});
});
}
if ($from = $request->getFrom()) {
$builder->where('published_at', '>=', $from);
}
if ($to = $request->getTo()) {
$builder->where('published_at', '<=', $to);
}
$builder->orderByDesc('published_at');
return $builder;
}
}
パターン2
Feature
<?php
namespace Tests\Feature\Admin;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\FeatureAdminTestCase;
class AuthorTest extends FeatureAdminTestCase
{
use RefreshDatabase;
public function setUp()
{
parent::setUp();
$this->login();
}
/**
* @test
*/
public function searchAuthors()
{
$param = [
'searchQuery' => 'slug'
];
$response = $this->get(
route('admin.authors.index'),
$param
);
$response->assertResponseStatus(200);
}
}
Unit(model)
<?php
namespace Tests\Unit\Models;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Oms\Models\Author;
use Tests\TestCase;
class AuthorSearchTest extends TestCase
{
use RefreshDatabase;
/**
* @test
*/
public function search_氏名を検索できること()
{
factory(Author::class)->create([
'name' => 'テスト太郎',
]);
factory(Author::class)->create([
'name' => 'テスト花子',
]);
// 部分一致で1件ヒットする
$actual = Author::search(['スト太']);
$this->assertCount(1, $actual);
// 前方一致で2件ヒットする
$actual = Author::search(['テスト']);
$this->assertCount(2, $actual);
}
/**
* @test
*/
public function search_ふりがなで検索できること()
{
factory(Author::class)->create([
'name_ruby' => 'てすとたろう'
]);
factory(Author::class)->create([
'name_ruby' => 'てすとはなこ'
]);
// 部分一致で1件ヒットする
$actual = Author::search(['とたろ']);
$this->assertCount(1, $actual);
// 前方一致で2件ヒットする
$actual = Author::search(['てすと']);
$this->assertCount(2, $actual);
}
/**
* @test
*/
public function search_スラッグで検索できること()
{
factory(Author::class)->create([
'slug' => 'slug_fugafuga_test',
]);
factory(Author::class)->create([
'slug' => 'slug_hogepiyo_test',
]);
// 部分一致で1件ヒットする
$subject = Author::search(['fugafuga']);
$this->assertCount(1, $subject);
// 前方一致で2件ヒットする
$subject = Author::search(['slug']);
$this->assertCount(2, $subject);
}
/**
* @test
*/
public function search_肩書きで検索できること()
{
factory(Author::class)->create([
'position' => '記事座長',
]);
factory(Author::class)->create([
'position' => '記事局長',
]);
$subject = Author::search(['記事座']);
$this->assertCount(1, $subject);
$subject = Author::search(['記事']);
$this->assertCount(2, $subject);
}
/**
* @test
*/
public function search_プロフィールで検索できること()
{
factory(Author::class)->create([
'position' => '大学卒業後、大手機械メーカーに就職。現在に至る。ふがふが',
]);
factory(Author::class)->create([
'position' => 'アメリカの学校を卒業後、フリーライターとして活躍し現在に至る。大学非常勤講師。ほげぴよ',
]);
// 部分一致で1件ヒットする
$subject = Author::search(['アメリカの学校']);
$this->assertCount(1, $subject);
// 部分一致で2件ヒットする
$subject = Author::search(['大学']);
$this->assertCount(2, $subject);
}
/**
* @test
*/
public function search_項目を跨いで検索できること()
{
$factory = factory(Author::class);
// それぞれのパターンで1件ずつレコード生成
$patterns = [
'name' => 'search_text',
'name_ruby' => 'search_text',
'slug' => 'slug_search_text_test',
'position' => 'search_text取締役',
'profile' => '大学卒業後、大手機械メーカーに就職。現在に至る。search_text',
];
foreach ($patterns as $column => $pattern) {
$factory->create([
$column => $pattern,
]);
}
$actual = Author::search(['search_text']);
$this->assertCount(5, $actual);
}
}
Unit(Request)
<?php
namespace Tests\Unit\Requests\Admin;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Oms\Http\Requests\Admin\AuthorSearchRequest;
class AuthorSearchRequestTest extends TestCase
{
/**
* @test
*/
public function getSearchWords_半角スペース区切りの文字列を配列で返す()
{
$request = new AuthorSearchRequest();
$request->merge(['searchQuery' => 'test1 test2 test3']);
$this->assertEquals(['test1', 'test2', 'test3'], $request->getSearchWords());
}
/**
* @test
*/
public function validate_255文字以上はバリデーションエラーになる()
{
$request = new AuthorSearchRequest();
$validator = app()->make('validator')->make(['searchQuery' => str_repeat('a', 256)], $request->rules());
$this->assertTrue($validator->fails());
}
}
・DIコンテナのサービスを使用する際に
protected static function getFacadeAccessor()
で定義されている文字列がキーになっていて、それを使用するときはそのキーを指定・キー自体はFacadeクラスが小文字でキーを持っている
バッチ
Unit/Condole/Commands
<?php
namespace Tests\Unit\Console\Commands;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Schema;
use Oms\Models\Customer;
use Oms\Models\Interest;
use Tests\TestCase;
class AddCustomerInterestsTest extends TestCase
{
// 蓄電池・水素・アンモニア・CO2回収貯留
const TARGET_INTEREST_ID = 5;
// 蓄電・省エネ
const ADD_INTEREST_ID = 6;
public function setUp(): void
{
parent::setUp();
$this->prepareInterests();
}
/**
* @test
*/
public function handle_関心事項の蓄電池_水素_アンモニア_CO2回収貯留にチェックをつけている会員を抽出し_蓄電_省エネにもチェックをつける()
{
// 会員情報作成
$customer = factory(Customer::class)->create();
// 関心事項の「蓄電池・水素・アンモニア・CO2回収貯留」にチェックをつけておく
$customer->interests()->attach(self::TARGET_INTEREST_ID);
// コマンド実行
$this->artisan('oms:insert_interest_id');
// 関心事項の「蓄電・省エネ」にもチェックがついていることを確認
$this->assertDatabaseHas('customer_interest', [
'customer_id' => $customer->id,
'interest_id' => self::ADD_INTEREST_ID,
]);
}
/**
* @test
*/
public function handle_既にチェックがついている会員はそのまま()
{
// 会員情報作成
$customer = factory(Customer::class)->create();
// 関心事項にチェックをつけておく
$customer->interests()->sync([1, 2, self::TARGET_INTEREST_ID, self::ADD_INTEREST_ID]);
// コマンド実行
$this->artisan('oms:insert_interest_id');
$customer->refresh();
$interests = $customer->interests->pluck('id')->toArray();
$this->assertEquals([1, 2, self::TARGET_INTEREST_ID, self::ADD_INTEREST_ID], $interests);
}
/**
* @test
*/
public function handle_関心事項の蓄電池_水素_アンモニア_CO2回収貯留にチェックをつけていない会員は関心事項の蓄電_省エネにチェックをつけない()
{
// 会員情報作成
$customer = factory(Customer::class)->create();
// コマンド実行
$this->artisan('oms:insert_interest_id');
// 関心事項の「蓄電・省エネ」にチェックがついている会員がいないことを確認
$this->assertDatabaseMissing('customer_interest', [
'customer_id' => $customer->id,
'interest_id' => self::ADD_INTEREST_ID,
]);
}
private function prepareInterests(): void
{
Schema::disableForeignKeyConstraints();
Interest::truncate();
factory(Interest::class, 4)->create(); // 適当な関心事項を4つ作成
Interest::create(['name' => '蓄電池・水素・アンモニア・CO2回収貯留']); // ID:5
Interest::create(['name' => '蓄電・省エネ']); // ID:6
}
}
Repository
<?php
namespace Tests\Unit\Repositories;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Oms\Models\Author;
use Oms\Models\Post;
use Oms\UseCase\Front\Search\FrontSearchRequest;
use Oms\Models\SearchTarget;
use Oms\Repositories\Interfaces\SearchTargetRepositoryInterface;
use Oms\Repositories\SearchTargetRepository;
class SearchTargetRepositoryTest extends TestCase
{
use RefreshDatabase;
/**
* @test
*/
public function bind()
{
$repository = app(SearchTargetRepositoryInterface::class);
$this->assertThat($repository, $this->isInstanceOf(SearchTargetRepository::class));
}
/**
* @test
*/
public function searchAuthor()
{
/** @var SearchTargetRepository $repository */
$repository = app(SearchTargetRepository::class);
// 著者データと中間テーブルの必須データ生成
$authors[] = factory(Author::class)->create([
'name' => 'Aさん',
'name_ruby' => 'えーさん',
]);
$authorIdsWithSort[] = [
$authors[0]->id => ['sort' => 1],
];
$authors[] = factory(Author::class)->create([
'name' => 'Bさん',
'name_ruby' => 'びーさん',
]);
$authorIdsWithSort[] = [
$authors[1]->id => ['sort' => 1],
];
$authors[] = factory(Author::class)->create([
'name' => 'Cさん',
'name_ruby' => 'しーさん',
]);
$authorIdsWithSort[] = [
$authors[2]->id => ['sort' => 1],
];
// 公開記事データ生成(著者データと同じ数)
$posts = factory(Post::class, count($authors))->create();
$search_targets = factory(SearchTarget::class);
foreach ($posts as $index => $post) {
$post->authors()->syncWithoutDetaching($authorIdsWithSort[$index]);
// 検索対象の記事データ生成
$search_targets->create(
[
'targetable_id' => $post->id,
'targetable_type' => 'Oms\Models\Post',
]
);
}
// 部分一致で2件ヒット
$actual = $repository->search((new FrontSearchRequest())->query('Aさ しー')->build())->get();
$this->assertCount(2, $actual);
}
}
コメント