Controller

建立 Controller 與 View :

$ php artisan make:controller ArticleController --plain
$ mkdir -p resources/views/articles
$ touch resources/views/articles/index.blade.php

如果不加 --plain ,會預設加入 indexcreatestore ... 等操作 resource 的相關方法。

ArticleController.php 中加入:

    public function index()
    {
        $articles = [];
        return view('articles.index', compact('articles');
    }

編輯 app/Http/routes.php ,將已定義的 routes 暫時註解掉,再加入:

Route::resource('articles', 'ArticleController');

php artisan route:list 確認有沒有正確加入。

建立 Controller 測試

  • 測試流程邏輯
  • 測試 HTTP 狀態

把原來的 tests/ExampleTest.php 改名:

$ mv tests/ExampleTest.php tests/ArticleControllerTest.php

編輯 tests/ArticleControllerTest.php

class ArticleControllerTest extends TestCase {

    public function testArticleList()
    {
        // 用 GET 方法瀏覽網址 /post
        $this->call('GET', '/posts');

        // 改用 Laravel 內建方法
        // 實際就是測試是否為 HTTP 200
        $this->assertResponseOk();

        // 應取得 articles 變數
        $this->assertViewHas('articles');
    }

}

測試成功。

  • 透過 call 方法執行 route,進而建立 Controller 實體來測試。
  • Laravel 有內建一些測試 response 狀態的 assert 方法。
  • session 或 cache 直接使用 array

注入 Repository 到 Controller 中

文章實際會從 ArticleRepository 裡取得,所以 Controller 會需要注入 Repository 。

namespace App\Http\Controllers;

use App\Repositories\ArticleRepository;
use Illuminate\Http\Response;

class ArticleController extends Controller {

    protected $repository;

    // 利用 Service Container (DI) 來自動注入 ArticleRepository
    public function __construct(ArticleRepository $repository)
    {
        $this->repository = $repository;
    }

    // ...
}

修改 ArticleController::index

    public function index()
    {
        // 改成從 ArticleRepository 中取得資料
        $articles = $this->repository->latest10();

        return view('article.index', compact('articles'));
    }

測試不成功,因為我們沒有連接資料庫。

用 Mockery 隔離 ArticleRepository

安裝 Mockery :

$ composer require mockery/mockery --dev
  • 不讓 Controller 測試接觸資料庫或其他需要 IO 的媒介
  • 利用 Mockery 透過 ArticleRepository 生成假物件 (mock object)
  • 利用 Service Container 注入假物件取代原本應該被呼叫的物件
  • 讓假物件的方法回傳假值
class ArticleControllerTest extends TestCase {

    protected $repositoryMock = null;

    public function setUp()
    {
        parent::setUp();

        // Mockery::mock 可以利用 Reflection 機制幫我們建立假物件
        $this->repositoryMock = Mockery::mock('App\Repositories\ArticleRepository');

        // Service Container 的 instance 方法可以讓我們
        // 用假物件取代原來的 ArticleRepository 物件
        $this->app->instance('App\Repositories\ArticleRepository', $this->repositoryMock);
    }

    public function tearDown()
    {
        // 每次完成 test case 後,要清除掉被 mock 的假物件
        Mockery::close();
    }

    public function testArticleList()
    {
        // 確認程式會呼叫一次 ArticleRepository::latest10 方法
        // 實際上是為這個 mock object 加入 latest10 方法
        // 沒有呼叫到的話就會發出異常
        // 再假設它會回傳 foo 這個字串
        // 這樣就不需要真的去連結資料庫
        $this->repositoryMock
            ->shouldReceive('latest10')
            ->once()
            ->andReturn([]);

        $this->call('GET', '/');
        $this->assertResponseOk();

        // 應取得 articles 變數
        // 而其值為空陣列
        $this->assertViewHas('articles', []);
    }
}

新增資料的測試

  • 程式中會透過 Repository 來新增資料,所以也要 mock 新增方法。
  • 因為有用到 POST 方法,所以要考慮 CSRF
  • 新增完成後要導向列表頁

先確認 CSRF 的保護機制是有作用的:

    public function testCsrfFailed()
    {
        // 模擬沒有 token 時
        // 程式應該是輸出 500 Error
        $this->call('POST', 'articles');
        $this->assertResponseStatus(500);
    }

加入 ArticleControllerTest::testCreateArticleSuccess

use Illuminate\Support\Facades\Session;

class ArticleControllerTest extends TestCase {

    // ...

    // 測試新增資料成功時的行為
    public function testCreateArticleSuccess()
    {
        // 會呼叫到 ArticleRepository::create
        $this->repositoryMock
            ->shouldReceive('create')
            ->once();

        // 初始化 Session ,因為需要避免 CSRF 的 token
        Session::start();

        // 模擬送出表單
        $this->call('POST', 'articles', [
            'title' => 'title 999',
            'body' => 'body 999',
            '_token' => csrf_token(), // 手動加入 _token
        ]);

        // 完成後會導向列表頁
        $this->assertRedirectedToRoute('articles.index');
    }

完成新增功能,也就是 store 方法。而 store 方法也會透過 Service Container 來注入 HTTP Request 物件。

use Illuminate\Http\Request;

class ArticleController extends Controller {

    // ...

    /**
     * Store a newly created resource in storage.
     *
     * @param Request $request
     * @return Response
     */
    public function store(Request $request)
    {
        // 直接從 Http\Request 取得輸入資料
        $this->repository->create($request->all());

        // 導向列表頁
        return Redirect::route('articles.index');
    }

results matching ""

    No results matching ""