0.寫在前面
- 本系列文章為
laracasts.com
的系列視訊教程——Let's Build A Forum with Laravel and TDD 的學習筆記。若喜歡該系列視訊,可去該網站訂閱後下載該系列視訊, 支援正版 。 - 視訊原始碼地址:https://github.com/laracasts/Lets-Build-a-Forum-in-Laravel
- *本專案為一個 forum(論壇)專案,與本站的第二本實戰教程 Laravel 教程 - Web 開發實戰進階 ( Laravel 5.5 ) 類似,可互相參照
- 專案開發模式為
TDD
開發,教程簡介為:A forum is a deceptively complex thing. Sure, it's made up of threads and replies, but what else might exist as part of a forum? What about profiles, or thread subscriptions, or filtering, or real-time notifications? As it turns out, a forum is the perfect project to stretch your programming muscles. In this series, we'll work together to build one with tests from A to Z.
- 專案版本為
laravel 5.4
,教程後面會進行升級到laravel 5.5
的教學 - 視訊教程共計 102 個小節,章節內容與視訊教程一一對應
1.本節說明
- 對應視訊第 8 小節:The Exception Handling Conundrum
2.本節內容
目前我們有關Thread
的路由為:web.php
.
.
Route::get('/threads','ThreadsController@index');
Route::post('/threads','ThreadsController@store');
Route::get('/threads/{thread}','ThreadsController@show');
.
.
對於Thread
,我們有完整的CURD
操作,可以將Thread
視作一個資源。
Laravel 遵從 RESTful 架構的設計原則,將資料看做一個資源,由 URI 來指定資源。對資源進行的獲取、建立、修改和刪除操作,分別對應 HTTP 協議提供的 GET、POST、PATCH 和 DELETE 方法。當我們要檢視一個 id 為 1 的使用者時,需要向 /users/1 地址傳送一個 GET 請求,當 Laravel 的路由接收到該請求時,預設會把該請求傳給控制器的 show 方法進行處理。
因此我們將與Thread
相關的路由簡寫為:
Route::resource('threads','ThreadsController');
以上程式碼等同於:
Route::get('/threads', 'threadsController@index')->name('threads.index');
Route::get('/threads/{thread}', 'threadsController@show')->name('threads.show');
Route::get('/threads/create', 'threadsController@create')->name('threads.create');
Route::post('/threads', 'threadsController@store')->name('threads.store');
Route::get('/threads/{thread}/edit', 'threadsController@edit')->name('threads.edit');
Route::patch('/threads/{thread}', 'threadsController@update')->name('threads.update');
Route::delete('/threads/{thread}', 'threadsController@destroy')->name('threads.destroy');
執行測試:
$ APP_ENV=testing phpunit
在之前的章節中,我們為新建thread
編寫了測試程式碼,且成功通過。現在讓我們完成此功能的程式碼:\app\Http\Controllers\ThreadsController.php
.
.
public function create()
{
return view('threads.create');
}
.
.
\resources\views\threads\create.blade.php
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Create a New Thread</div>
<div class="panel-body">
<form method="post" action="/threads">
{{ csrf_field() }}
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" id="title" name="title">
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea name="body" id="body" class="form-control" rows="8"></textarea>
</div>
<button type="submit" class="btn btn-primary">Publish</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
我們在編寫程式碼之前就為thread
的新建動作編寫了測試,且成功通過,這意味著我們釋出thread
是不會遇到什麼功能上的問題的:
在當前情況下,我們在ThreadsController
的__construct
方法中使用了白名單機制only
來做許可權控制,但這樣不太安全。我們現在改用黑名單機制,即除了index
,show
方法,其他方法都需要進行登入才能操作。\app\Http\Controllers\ThreadsController.php
class ThreadsController extends Controller
{
public function __construct()
{
$this->middleware('auth')->except(['index','show']);
}
.
.
我們來編寫一個功能測試,用來測試未登入使用者訪問 http://forum.test/threads/create 頁面。測試邏輯應為:使用者訪問頁面,如未登入,重定向到 登入頁面 。\tests\Feature\CreateThreadsTest.php
.
.
/** @test */
public function guests_may_not_see_the_create_thread_page()
{
$this->get('/threads/create')
->assertRedirect('/login');
}
.
.
讓我們來執行一下測試:
丟擲了Unauthenticated
的異常,這與我們的測試初衷不符。我們曾在Handler.php
做過設定,如果app()->environment() === 'testing'
就會丟擲異常:\app\Exceptions\Handler.php
.
.
public function render($request, Exception $exception)
{
if (app()->environment() === 'testing') throw $exception;
return parent::render($request, $exception);
}
.
.
在類似於未登入使用者訪問 http://forum.test/threads/create 頁面的測試中,我們需要放行,而不是丟擲異常。現在我們來進行處理:\forum\tests\TestCase.php
<?php
namespace Tests;
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
use Illuminate\Contracts\Debug\ExceptionHandler;
use App\Exceptions\Handler;
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
protected function setUp()
{
parent::setUp();
$this->disableExceptionHandling();
}
protected function signIn($user = null)
{
$user = $user ?: create('App\User');
$this->actingAs($user);
return $this;
}
protected function disableExceptionHandling()
{
$this->oldExceptionHander = $this->app->make(ExceptionHandler::class);
$this->app->instance(ExceptionHandler::class,new class extends Handler{
public function __construct(){}
public function report(\Exception $e){}
public function render($request,\Exception $e){
throw $e;
}
});
}
protected function withExceptionHandling()
{
$this->app->instance(ExceptionHandler::class,$this->oldExceptionHandler);
return $this;
}
}
注:不要忘了頭部的引用
對於繼承TestCase
基類的測試用例,我們預設先呼叫disableExceptionHandling()
方法。該方法對Handler.php
的內容進行了重寫,預設丟擲異常。當我們不需要丟擲異常時,繼續呼叫withExceptionHandling()
方法即可。
現在將\app\Exceptions\Handler.php
中的下面這行程式碼刪掉:
if (app()->environment() === 'testing') throw $exception;
同時在編寫的測試用例中呼叫withExceptionHandling()
方法:\tests\Feature\CreateThreadsTest.php
.
.
/** @test */
public function guests_may_not_see_the_create_thread_page()
{
$this->withExceptionHandling() // 此處呼叫
->get('/threads/create')
->assertRedirect('/login');
}
.
.
再次測試,測試通過:
3.寫在後面
- 如有建議或意見,歡迎指出~
- 如果覺得文章寫的不錯,請點贊鼓勵下哈,你的鼓勵將是我的動力!
本作品採用《CC 協議》,轉載必須註明作者和本文連結