PHPUnit Laravel テスト

パターン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);
    }
}

コメント

タイトルとURLをコピーしました