[Laravel8.x] postした後にredirectするかのテストコード

sessionでflashをつけてリダイレクトしているが、きちんとリダイレクトされるかのテストコードを書く

controller側

    public function testFormConfirm(Request $request){
        if($request->has('name') & $request->name !== NULL){
            session()->flash('flashmessage',$request->name. 'さん、登録完了');
        } else {
            session()->flash('flashmessage','エラー: 名前を入力してください');
        }

        return redirect('/admin/test/form');
    }

test code

    public function testExample()
    {
        // $this->visit('/admin/test/form')->see('名前');

        $response = $this->post('/admin/test/form/confirm', [
            'name' => 'hpscript'
        ]);

        $response->assertRedirect('/admin/test/form');
    }

$ vendor/bin/phpunit tests/Feature/AdminTest.php
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 00:01.530, Memory: 34.00 MB

OK (1 test, 2 assertions)

なるほど、仕組みはわかってきた。keep goin.

[Laravel8.x] viewがきちんと表示されるかのテスト

route

Route::get('/admin/test/form', [App\Http\Controllers\AdminController::class, 'testForm']);

controller

    public function testForm(){

        return view('admin.test');
    }

testCase

    public function testExample()
    {
        $this->visit('/admin/test/form')->see('名前');
    }

$ vendor/bin/phpunit tests/Feature/AdminTest.php
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

E 1 / 1 (100%)

Time: 00:01.715, Memory: 32.00 MB

There was 1 error:

1) Tests\Feature\AdminTest::testExample
Error: Call to undefined method Tests\Feature\AdminTest::visit()

/home/vagrant/dev/testApp/tests/Feature/AdminTest.php:26

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

ん? visitを使うにはduskを入れないと駄目らしい。
と言うことで、公式を見ます。

https://laravel.com/docs/8.x/dusk
$ composer require –dev laravel/dusk
$ php artisan dusk:install
$ php artisan dusk:chrome-driver

1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownErrorException: unknown error: cannot find Chrome binary

/home/vagrant/dev/testApp/vendor/php-webdriver/webdriver/lib/Exception/WebDriverException.php:139
/home/vagrant/dev/testApp/vendor/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.php:371
/home/vagrant/dev/testApp/vendor/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.php:136
/home/vagrant/dev/testApp/tests/DuskTestCase.php:43
/home/vagrant/dev/testApp/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:218
/home/vagrant/dev/testApp/vendor/laravel/framework/src/Illuminate/Support/helpers.php:234
/home/vagrant/dev/testApp/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:219
/home/vagrant/dev/testApp/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:97
/home/vagrant/dev/testApp/vendor/laravel/dusk/src/Concerns/ProvidesBrowser.php:65
/home/vagrant/dev/testApp/tests/Browser/ExampleTest.php:22

うーん、なんでやろ。。ちょっとよくわからんな。

[Laravel8.x] テスト用データベースでUnitテスト

Unitテストでは、テスト用のデータベースを作り、それでテストする。
Schemaを使う。

mysql> show databases;
mysql> show tables;

mysql> create database unittest;
$ php artisan migrate –env=testing

mysql> use unittest;
mysql> describe users;

use Illuminate\Support\Facades\Schema;
    public function testExample()
    {
        $this->assertTrue(
            Schema::hasColumns('users',[
                'id', 'name', 'email'
            ]),
            1
        );
    }

$ vendor/bin/phpunit tests/Feature/AdminTest.php
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 00:00.086, Memory: 26.00 MB

OK (1 test, 1 assertion)

DBにデータ挿入

    public function testExample()
    {
        $user = new User();
        $user->name ="hpscript";
        $user->email = "info@hpscript.com";
        $user->password = "password";
        $user->role_id = 1;
        $saveUser = $user->save();

        $this->assertTrue($saveUser);
    }

$ vendor/bin/phpunit tests/Feature/AdminTest.php
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 00:00.094, Memory: 26.00 MB

OK (1 test, 1 assertion)

mysql> select * from users;
+—-+———-+——————-+——————-+———-+——————-+—————————+—————-+—————+—————–+——————–+———————+———————+———+
| id | name | email | email_verified_at | password | two_factor_secret | two_factor_recovery_codes | remember_token | last_login_at | current_team_id | profile_photo_path | created_at | updated_at | role_id |
+—-+———-+——————-+——————-+———-+——————-+—————————+—————-+—————+—————–+——————–+———————+———————+———+
| 1 | hpscript | info@hpscript.com | NULL | password | NULL | NULL | NULL | NULL | NULL | NULL | 2021-01-03 16:44:08 | 2021-01-03 16:44:08 | 1 |
+—-+———-+——————-+——————-+———-+——————-+—————————+—————-+—————+—————–+——————–+———————+———————+———+
1 row in set (0.00 sec)

おおお、入ってる。

use RefreshDatabase;とすると、データが削除されます。

mysql> select * from users;
Empty set (0.00 sec)

なるほど、ちょっと理解してきた。

[Laravel8.x] Unitテスト基礎

単体テストがイマイチ腹に落ちない。
やりたいことは、controllerのメソッド単位でのテスト

$ php artisan make:test AdminTest
/tests/Feature/AdminTest.php

    public function testExample()
    {
        $response = $this->get('/');

        $response->assertStatus(200);
    }

$ vendor/bin/phpunit tests/Feature/AdminTest.php
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 00:00.144, Memory: 26.00 MB

OK (1 test, 1 assertion)

route/web.php

Route::get('/admin/test/form', [App\Http\Controllers\AdminController::class, 'testForm']);

AdminController

    public function testForm(){
        return "hello";
        // return view('admin.test');
    }
    public function testExample()
    {
        $response =  $this->get('/admin/test/form');
        $response->assertSee("hello");
    }

$ vendor/bin/phpunit tests/Feature/AdminTest.php
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

. 1 / 1 (100%)

Time: 00:00.086, Memory: 26.00 MB

OK (1 test, 1 assertion)

なるほど、$response->assertSee(); で確認するのか。assertEqualsとかassertSameで確認してた。

$ vendor/bin/phpunit tests/Feature/AdminTest.php

use App\Models\User;
public function testExample()
    {
        // $response = $this->get('/');
        $response =  User::all(); 
        dd($response);
        
    }

$ vendor/bin/phpunit tests/Feature/AdminTest.php
PHPUnit 9.4.2 by Sebastian Bergmann and contributors.

Illuminate\Database\Eloquent\Collection^ {#1678
#items: []
}
なんでnullになるのか理解できん。
データベースのテストの方法が違うっぽいな。

[Laravel8.16.0] UserAgentを取得してPC/SPのviewを分けて表示する

UserAgentの取得には、jenssegers/agentを使います。

github: https://github.com/jenssegers/agent

$ composer require jenssegers/agent

config/app.php

    'providers' => [
        // 省略
        Jenssegers\Agent\AgentServiceProvider::class,
    ],

'aliases' => [
        //省略
        'Agent' => Jenssegers\Agent\Facades\Agent::class,
    ],

AdminController.php

use Jenssegers\Agent\Agent;

    public function useragent(){
        if(\Agent::isMobile()){
            return view('admin.sp');
        }
        return view('admin.pc');
    }


なるほど、相当簡単に実装できる。
これをunitテストでやりたい。

$ php artisan make:test UserRegisterTest

public function testExample()
    {
        $response = $this->get('/');

        $response->dumpHeaders();
    }

$ vendor/bin/phpunit tests/Feature/UserAgentTest.php

public function testExample()
    {
        $response = $this->get('/admin/useragent');
        // $response->assertStatus(200);
        $this->assertSame(2, $response);
    }

$ vendor/bin/phpunit tests/Feature/UserAgentTest.php
FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

なんでだ? $this->getだとheader情報しか返ってこないのか。
UnitTestはもうちょっとやりこまないとダメやな。

[Laravel8.16.0] ユーザ登録のテストコードを書く

$ cp .env.example .env.testing
$ php artisan key:generate –env=testing
// test用のDB接続
$ vi .env.testing

$ rm ./tests/Feature/ExampleTest.php
$ rm ./tests/Unit/ExampleTest.php

app/Actions/Fortify/CreateNewUser.php

return User::create([
            'name' => $input['name'],
            'company' => $input['company'],
            'role_id' => 1,
            'email' => $input['email'],
            'password' => Hash::make($input['password']),
        ]);

CreateNewUser.phpと同じようなことを書いていく
$ php artisan make:test UserRegisterTest

tests/Feature/UserRegisterTest.php

public function testUserRegister()
    {
        $email = 'hogehoge@gmail.com';
        $this->post(route('register'), [
            'name' => 'testuser',
            'company' => 'test company',
            'email' => $email,
            'password' => 'password',
            'password_confirmation' => 'password'
        ])
            ->assertStatus(302);
 
        $this->assertDatabaseHas('users', ['email' => $email]);
    }

$ vendor/bin/phpunit –testdox
PHPUnit 9.4.3 by Sebastian Bergmann and contributors.

User Register (Tests\Feature\UserRegister)
✔ User register

Time: 00:00.354, Memory: 26.00 MB

OK (1 test, 2 assertions)

なるほど、わかったようなわかってないようなw

amazon linux2(vagrant) + Laravel 7.xでDuskを使うまでの手順

Laravelでseleniumを使おうと思ったら、duskというパッケージがあるらしいので、duskをインストールして使っていきます。環境は、vagrant、amazon linux2, laravel 7.2.1です。

### 1.duskインストール
$ php composer.phar require –dev laravel/dusk
– laravel/dusk v5.10.0 requires ext-zip * -> the requested PHP extension zip is missing from your system.

$ sudo yum install php-pecl-zip.x86_64
$ php composer.phar require –dev laravel/dusk

$ php artisan dusk:install
testsディレクトリにBrowserディレクトリが作られる

.env

APP_URL=http://192.168.33.10:8000

### 2.duskテスト
$ php artisan dusk:make LoginTest

/tests/Browser/LoginTest.php

public function test_can_login_successfull(){
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                ->type('email', 'foo@hoge.com')
                ->type('password', 'hogehoge')
                ->press('Login')
                ->assertSee('this is subscriber');
        });
    }
$ php artisan dusk
1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\WebDriverCurlException: Curl error thrown for http POST to /session with params: {"capabilities":{"firstMatch":[{"browserName":"chrome","goog:chromeOptions":{"binary":"","args":["--disable-gpu","--headless","--window-size=1920,1080"]}}]},"desiredCapabilities":{"browserName":"chrome","platform":"ANY","chromeOptions":{"binary":"","args":["--disable-gpu","--headless","--window-size=1920,1080"]}}}

Failed to connect to localhost port 9515: Connection refused

ん? ChromeDriverが入っていない?

### 3.ChromeDriver インストール
$ ./vendor/laravel/dusk/bin/chromedriver-linux –verbose
./vendor/laravel/dusk/bin/chromedriver-linux: error while loading shared libraries: libX11.so.6: cannot open shared object file: No such file or directory
$ sudo yum install -y libX11
$ ./vendor/laravel/dusk/bin/chromedriver-linux –v
ChromeDriver 80.0.3987.106 (f68069574609230cf9b635cd784cfb1bf81bb53a-refs/branch-heads/3987@{#882})

$ php artisan dusk
1) Tests\Browser\ExampleTest::testBasicExample
Facebook\WebDriver\Exception\UnknownErrorException: unknown error: cannot find Chrome binary

なに、今度はChrome binary?

### 4.Chrome install
$ curl https://intoli.com/install-google-chrome.sh | bash
$ php artisan dusk

1) Tests\Browser\LoginTest::test_can_login_successfull
Facebook\WebDriver\Exception\NoSuchElementException: no such element: Unable to locate element: {"method":"css selector","selector":"body textarea[name='email']"}
  (Session info: headless chrome=80.0.3987.149)

どういうこと? 

### 5.visitでパスを直接指定

public function test_can_login_successfull(){
        $this->browse(function (Browser $browser) {
            $browser->visit('http://192.168.33.10:8000/login')
                // ->dump();
                ->type('email', 'foo@hoge.com')
                ->type('password', 'hogehoge')
                ->press('Login')
                ->assertSee('this is subscriber');
        });

    }
$ php artisan dusk
PHPUnit 8.5.2 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 2.18 seconds, Memory: 18.00 MB

OK (1 test, 1 assertion)

上手くいきました。半日かかりましたが、seleniumが何やってるかは理解できました。

Laravel7.x : Expected status code 200 but received 302.

Circle CIでテスト

There was 1 failure:

1) Tests\Feature\ExampleTest::testBasicTest
Expected status code 200 but received 302.
Failed asserting that false is true.

/home/circleci/repo/vendor/laravel/framework/src/Illuminate/Testing/TestResponse.php:185
/home/circleci/repo/tests/Feature/ExampleTest.php:19

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

リダイレクト処理で302になっている為。
tests/Feature/ExampleTest.php

    public function testBasicTest()
    {
        $response = $this->get('/');

        // $response->assertStatus(200);
        $response->assertStatus(302);
    }

-> SUCCESS

OK^^

wip TDD、tailwindcss, @errorの書き方

wip: work in progress

refactoring

public function test_an_author_can_be_created(){

        $this->post('/authors', $this->data());
        $author = Author::all();

        $this->assertCount(1, $author);
        $this->assertInstanceOf(Carbon::class, $author->first()->dob);
        $this->assertEquals('1988/14/05', $author->first()->dob->format('Y/d/m'));
    }

    private function data(){
        return [
            'name' => 'Author Name',
            'dob' => '05/14/1988'
        ];
    }

$ phpunit –filter test_an_author_can_be_created

public function test_a_name_is_required(){
        $response = $this->post('/authors', array_merge($this->data(), ['name' => '']));

        $response->assertSessionHasErrors('name');

    }

    public function test_a_dob_is_required(){
        $response = $this->post('/authors', array_merge($this->data(), ['dob' => '']));
        $response->assertSessionHasErrors('dob');

    }

controller

public function store(){
    	$data = $this->validateData();

    	Author::create($data);
    }

    protected function validateData(){
    	return request()->vadidate([
    		'name' => 'required',
    		'dob'=> '',
    	]);
    }

### tailwindcss
$ php artisan help preset
$ php artisan preset none
$ php artisan preset vue

$ npm install

https://tailwindcss.com/
$ npm install tailwindcss –save-dev

app.scss

@tailwind base;

@tailwind components;

@tailwind utilities;

$ npx tailwind init

webpack.mix.js

const tailwindcss = require('tailwindcss');
mix.js('resources/js/app.js', 'public/js')
   .sass('resources/sass/app.scss', 'public/css')
   .options({
    processCssUrls: false,
    postCss: [ tailwindcss('./.config.js') ],
  });

$ npm install webpack

$ npm run dev

authors/create.blade.php

@extends('layouts.app')

@section('content')
	<dvi class="bg-gray-300 h-screen">
		asdfg
	</dvi>
@endsection

web.php

Route::get('/authors/create', 'AuthorsController@create');

create.blade.php
test

@extends('layouts.app')

@section('content')
	<div class="w-2/3 bg-gray-200 mx-auto">
		asdfg
	</div>
@stop

### @errorの
styling

<div class="w-2/3 bg-gray-200 mx-auto p-6 shadow">
		<form action="/authors" method="post" class="flex flex-col items-center">
			@csrf
		<h1>Add New Author</h1>
		<div class="pt-4">
			<input type="text" name="name" placeholder="Full Name" class="rounded px-4 py-2 w-64">
			@if{{$errors->has('dob')}}
				<p class="text-red-600">{{$errors->first('name')}}</p>
			@endif
		</div>
		<div class="pt-4">
			<input type="text" name="dob" placeholder="Date of Birth" class="rounded px-4 py-2 w-64">
			@if{{$errors->has('dob')}}
				<p class="text-red-600">{{$errors->first('dob')}}</p>
			@endif
		</div>
		<div class="pt-4">
			<button class="bg-blue-400 text-white rounded py-2 px-4">Add New Author</button>
		</div>
		</form>
	</div>
@error('name') <p class="text-red-600">{{$message}}</p> @enderror

tailwindはモダンな感じがする
しかし、6系になってフロント周りを中心に随分仕様が変わってます。

laravel feature test 404と6系のmake:auth

– laravel 6系ではmake:authが無くなりました
$ php artisan –version
Laravel Framework 6.9.0
– unit test -> feature testの順番

$ php artisan make:test BookCheckoutTest

use RefreshDatabase;
    public function test_a_book_can_be_check_out(){

        $this->withoutExceptionHandling();

        $book = factory(Book::class)->create();
        $this->actingAs($user = Factory(User::class)->create())
            ->post('/checkout/' . $book->id);

        $this->assertCount(1, Reservation::all());
        $this->assertEquals($user->id, Reservation::first()->user_id);
        $this->assertEquals($book->id, Reservation::first()->book_id);
        $this->assertEquals(now(), Reservation::first()->checked_out_at);
    }

route

Route::post('/checkout/{book}', 'CheckoutBookController@store');

$ php artisan make:controller CheckoutBookController

public function __construct(){
		$this->middleware('auth');
	}
public function store(Book $book){
    	$book->checkout(auth()->user());
    }

$ phpunit –filter test_a_book_can_be_check_out
OK (1 test, 4 assertions)

$ php artisan make:auth
Command “make:auth” is not defined.

### make:auth
$ composer require laravel/ui –dev
$ php artisan ui vue –auth
$ php artisan migrate:refresh
$ npm install

$ phpunit –filter test_only_signed_in_users_can_be_checkout_book
OK (1 test, 3 assertions)

$ php artisan make:controller CheckinBookController

public function __construct(){
		$this->middleware('auth');
	}
    public function store(Book $book){
    	$book->checkin(auth()->user());
    }
public function test_404_is_thrown_if_a_book_is_not_checked_out(){

        $book = factory(Book::class)->create();
        $user = factory(User::class)->create();

        $this->actingAs($user)
            ->post('/checkin/' . $book->id)
            ->assertStatus(404);

        $this->assertCount(0, Reservation::all());
    }
public function __construct(){
		$this->middleware('auth');
	}
    public function store(Book $book){

    	try {
    		$book->checkin(auth()->user());
    	} catch(\Exception $e){
    		return response([], 404);
    	}
    	
    }

make:authがvueのコンポーネントになったということは、LaravelはReactよりvueを支持しているってこと?フロントエンドは確実にvue.jsが必須になってきました。

それと、テストエンジニアってなんか敬遠されがちだが、そもそもtestはフレームワークの仕組みを深く理解してないとできないから、テストエンジニアを下に見るのは偏見がかなり入っている