1、簡介
Laravel 植根於測試,實際上,內建使用 PHPUnit對測試提供支援是即開即用的,並且 phpunit.xml檔案已經為應用設定好了。架構還提供了方便的輔助方法允許你對應用進行富有表現力的測試。
tests目錄中提供了一個 ExampleTest.php檔案,安裝完新的 Laravel 應用後,只需簡單在命令列運行 phpunit來運行測試。
1.1 測試環境
運行測試的時候,Laravel 自動化佈建配置環境為 testing。Laravel在測試時自動設定 session 和 cache 驅動為數組驅動,這意味著測試時不會持久化儲存 session 和 cache。
如果需要的話你也可以建立其它測試環境配置。 testing環境變數可以在 phpunit.xml檔案中配置。
1.2 定義&運行測試
要建立一個新的測試案例,可以使用如下Artisan命令:
php artisan make:test UserTest
該命令將會在 tests目錄下產生一個新的 UserTest類。然後你可以使用PHPUnit 定義測試方法。要運行測試,簡單從終端執行 phpunit命令即可:
assertTrue(true); }}
注意:如果你在測試類別中定義自己的 setUp方法,確保在其中調用 parent::setUp。
2、應用測試
Laravel 為產生 HTTP請求、測試輸出、以及填充表單提供了平滑的API。舉個例子,我們看下 tests目錄下包含的 ExampleTest.php檔案:
visit('/') ->see('Laravel 5') ->dontSee('Rails'); }}
visit方法產生了一個GET請求, see方法對我們從應用返迴響應中應該看到的給定文本進行斷言。 dontSee方法對給定文本沒有從應用響應中返回進行斷言。在Laravel中這是最基本的有效應用測試。
2.1 與應用互動
當然,除了對響應文本進行斷言之外還有做更多測試,讓我們看一些點選連結和填充表單的例子:
點選連結
在本測試中,我們將為應用產生請求,在返回的響應中“點擊”連結,然後對訪問URI進行斷言。例如,假定響應中有一個“關於我們”的連結:
About Us
現在,讓我們編寫一個測試點擊連結並斷言使用者訪問頁面是否正確:
public function testBasicExample(){ $this->visit('/') ->click('About Us') ->seePageIs('/about-us');}
處理表單
Laravel 還為處理表單提供了多個方法。 type, select, check, attach, 和 press方法允許你與所有表單輸入進行互動。例如,我們假設這個表單存在於應用註冊頁面:
我們可以編寫測試完成表單並檢查結果:
public function testNewUserRegistration(){ $this->visit('/register') ->type('Taylor', 'name') ->check('terms') ->press('Register') ->seePageIs('/dashboard');}
當然,如果你的表單包含其他輸入比如選項按鈕或下拉式清單,也可以輕鬆填寫這些欄位類型。這裡是所有表單操作方法列表:
| 方法 |
描述 |
| $this->type($text, $elementName) |
“Type” 文本到給定欄位 |
| $this->select($value, $elementName) |
“Select” 單選框或下拉式清單 |
| $this->check($elementName) |
“Check” 複選框 |
| $this->attach($pathToFile, $elementName) |
“Attach” 檔案到表單 |
| $this->press($buttonTextOrElementName) |
“Press” 給定文本或name的按鈕 |
| $this->uncheck($elementName) |
“Uncheck”複選框 |
處理附件
如果表單包含 file輸入類型,可以使用 attach方法添加檔案到表單:
public function testPhotoCanBeUploaded(){ $this->visit('/upload') ->name('File Name', 'name') ->attach($absolutePathToFile, 'photo') ->press('Upload') ->see('Upload Successful!');}
2.2 測試JSON API
Laravel 還提供多個協助函數用於測試 JSON API 及其響應。例如, get, post, put, patch, 和 delete方法用於通過多種 HTTP 要求方式發出請求。你還可以輕鬆傳遞資料和頭到這些方法。作為開始,我們編寫測試來產生 POST 請求到 /user並斷言返回的資料是否是 JSON 格式:
post('/user', ['name' => 'Sally']) ->seeJson([ 'created' => true, ]); }}
seeJson方法將給定數組轉化為 JSON,然後驗證應用返回的整個 JSON 響應中的 JSON 片段。因此,如果在 JSON 響應中有其他屬性,只要給定片段存在的話測試依然會通過。
驗證JSON值匹配
如果你想要驗證給定數組和應用返回的JSON能夠精確匹配,使用 seeJsonEquals方法:
post('/user', ['name' => 'Sally']) ->seeJsonEquals([ 'created' => true, ]); }}
驗證JSON資料結構匹配
還可以驗證JSON響應是否與指定資料結構匹配,我們使用 seeJsonStructure方法來實現這一功能:
get('/user/1') ->seeJsonStructure([ 'name', 'pet' => [ 'name', 'age' ] ]); }}
上面的例子示範了期望擷取一個包含 name和嵌套 pet對象(該對象包含 name和 age屬性)的 JSON 資料。如果 JSON 響應中包含其它額外鍵 seeJsonStructure也不會失敗,例如,如果 pet對象包含 weight屬性測試仍將通過。
你可以使用*來斷言返回JSON結構包含一個列表,該列表中的每個資料項目都包含至少如下樣本中列出的屬性:
get('/users') ->seeJsonStructure([ '*' => [ 'id', 'name', 'email' ] ]); }}
你還可以使用嵌套的*,在這種情境中,我們可以斷言JSON響應中的每個使用者都包含一個給定屬性集合,而且每個使用者的每個 pet都包含給定屬性集合:
$this->get('/users') ->seeJsonStructure([ '*' => [ 'id', 'name', 'email', `pets` => [ '*' => [ 'name', 'age' ] ] ] ]);
2.3 Session/認證
Laravel提供了多個輔助函數用於在測試期間處理Session,首先,可以使用 withSession方法設定 session 值到給定數組。這在測試請求前擷取 session 資料時很有用:
withSession(['foo' => 'bar']) ->visit('/'); }}
當然,session的通常用於操作使用者狀態,例如認證使用者。輔助函數 actingAs提供了認證給定使用者為目前使用者的簡單方法,例如,我們使用模型工廠產生和認證使用者:
create(); $this->actingAs($user) ->withSession(['foo' => 'bar']) ->visit('/') ->see('Hello, '.$user->name); }}
還可以通過傳遞 guard名稱作為 actingAs函數的第二個參數的方式來指定使用哪個 guard來認證給定使用者:
$this->actingAs($user, 'backend')
2.4 禁止中介軟體
測試應用時,為某些測試禁止中介軟體很方便。這種機制允許你將路由和控制器與中介軟體孤立開來做測試,Laravel包含了一個簡單的 WithoutMiddlewaretrait,可以使用該 trait 自動在測試類別中禁止所有中介軟體:
如果你只想在某些方法中禁止中介軟體,可以在測試方法中調用 withoutMiddleware方法:
withoutMiddleware(); $this->visit('/') ->see('Laravel 5'); }} 2.5 自訂HTTP請求
如果你想要在應用中產生自訂HTTP請求並擷取完整的 Illuminate\Http\Response對象,可以使用 call方法:
public function testApplication(){ $response = $this->call('GET', '/'); $this->assertEquals(200, $response->status());} 如果你要產生 POST, PUT, 或者 PATCH請求可以在請求中傳入輸入資料數組,在路由或控制器中可以通過Request執行個體訪問請求資料:
$response = $this->call('POST', '/user', ['name' => 'Taylor']); 2.6 PHPUnit斷言方法
Laravel 為 PHPUnit 測試提供了額外的斷言方法:
| 方法 |
描述 |
| ->assertResponseOk(); |
斷言用戶端響應狀態代碼是否為200 |
| ->assertResponseStatus($code); |
斷言用戶端響應狀態代碼是否是給定$code |
| ->assertViewHas($key, $value = null); |
斷言響應視圖是否包含給定的綁定資料片段 |
| ->assertViewHasAll(array $bindings); |
斷言視圖是否包含給定綁定資料列表 |
| ->assertViewMissing($key); |
斷言響應視圖缺失綁定資料片段 |
| ->assertRedirectedTo($uri, $with = []); |
斷言用戶端是否重新導向到給定URI |
| ->assertRedirectedToRoute($name, $parameters = [], $with = []); |
斷言用戶端是否重新導向到給定路由 |
| ->assertRedirectedToAction($name, $parameters = [], $with = []); |
斷言用戶端是否重新導向到給定action |
| ->assertSessionHas($key, $value = null); |
斷言session是否包含給定值 |
| ->assertSessionHasAll(array $bindings); |
斷言session是否保護眼給定值列表 |
| ->assertSessionHasErrors($bindings = [], $format = null); |
斷言session是否包含錯誤綁定 |
| ->assertHasOldInput(); |
斷言sessio包含上次輸入資料 |
3、處理資料庫
Laravel還提供了多種有用的工具讓測試資料庫驅動的應用更加簡單。首先,你可以使用協助函數 seeInDatabase來斷言資料庫中的資料是否和給定資料集合匹配。例如,如果你想要通過 email 值為 sally@example.com的條件去資料表 users查詢是否存在該記錄 ,我們可以這樣做:
public function testDatabase(){ // 調用應用... $this->seeInDatabase('users', ['email' => 'sally@foo.com']);} 當然, seeInDatabase方法和其它類似輔助方法都是為了方便起見進行的封裝,你也可以使用其它PHPUnit內建的斷言方法來進行測試。
3.1 每次測試後重設資料庫
每次測試後重設資料庫通常很有用,這樣的話上次測試的資料不會影響下一次測試。
使用遷移
一種方式是每次測試後復原資料庫並在下次測試前重新遷移。Laravel提供了一個簡單的 DatabaseMigrationstrait來自動為你處理。在測試類別上簡單使用該trait如下:
visit('/') ->see('Laravel 5'); }} 使用事務
另一種方式是將每一個測試案例包裹到一個資料庫事務中,Laravel提供了方便的 DatabaseTransactionstrait自動為你處理:
visit('/') ->see('Laravel 5'); }}
注意:該trait只在事務中封裝預設資料庫串連。
3.2 模型工廠
測試時,通常需要在執行測試前插入新資料到資料庫。在建立測試資料時,Laravel允許你使用“factories”為每個Eloquent模型定義預設的屬性值集合,而不用手動為每一列指定值。作為開始,我們看一下 database/factories/ModelFactory.php檔案,該檔案包含了一個工廠定義:
$factory->define(App\User::class, function (Faker\Generator $faker) { return [ 'name' => $faker->name, 'email' => $faker->email, 'password' => bcrypt(str_random(10)), 'remember_token' => str_random(10), ];}); 在閉包中,作為工廠定義,我們返回該模型上所有屬性預設測試值。該閉包接收PHP庫 Faker執行個體,從而允許你方便地為測試產生多種類型的隨機資料。
當然,你可以添加更多工廠到 ModelFactory.php檔案。
多個工廠類型
有時候你可能想要為同一個Eloquent模型類產生多個工廠,例如,除了正常使用者外可能你想要為“管理員”使用者產生一個工廠,你可以使用 defineAs方法定義這些工廠:
$factory->defineAs(App\User::class, 'admin', function ($faker) { return [ 'name' => $faker->name, 'email' => $faker->email, 'password' => str_random(10), 'remember_token' => str_random(10), 'admin' => true, ];}); 你可以使用 raw方法擷取基本屬性而不用重複基本使用者工廠中的所有屬性,擷取這些屬性後,只需將你要求的額外值增補進去即可:
$factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) { $user = $factory->raw(App\User::class); return array_merge($user, ['admin' => true]);}); 在測試中使用工廠
定義好工廠後,可以在測試或資料庫填充檔案中通過全域的 factory方法使用它們來產生模型執行個體,所以,讓我們看一些產生模型的例子,首先,我們使用 make方法,該方法建立模型但不將其儲存到資料庫:
public function testDatabase(){ $user = factory(App\User::class)->make(); // 使用者模型測試...} 如果你想要覆蓋模型的一些預設值,可以傳遞數組值到 make方法。只有指定值被替換,其他資料保持不變:
$user = factory(App\User::class)->make([ 'name' => 'Abigail',]);
還可以建立多個模型集合或者建立給定類型的集合:
// 建立3個 App\User 執行個體...$users = factory(App\User::class, 3)->make();// 建立1個 App\User "admin" 執行個體...$user = factory(App\User::class, 'admin')->make();// 建立3個 App\User "admin" 執行個體...$users = factory(App\User::class, 'admin', 3)->make();
持久化工廠模型
create方法不僅能建立模型執行個體,還可以使用Eloquent的 save方法將它們儲存到資料庫:
public function testDatabase(){ $user = factory(App\User::class)->create(); //使用者模型測試...} 你仍然可以通過傳遞數組到 create方法覆蓋模型上的屬性:
$user = factory(App\User::class)->create([ 'name' => 'Abigail',]);
添加關聯關係到模型
你甚至可以持久化多個模型到資料庫。在本例中,我們添加一個關聯到建立的模型,使用 create方法建立多個模型的時候,會返回一個Eloquent集合執行個體,從而允許你使用集合提供的所有便利方法,例如 each:
$users = factory(App\User::class, 3) ->create() ->each(function($u) { $u->posts()->save(factory(App\Post::class)->make()); }); 4、 類比
4.1 類比事件
如果你在重度使用Laravel的時間系統,可能想要在測試時類比特定事件。例如,如果你在測試使用者註冊,你可能不想所有 UserRegistered的時間處理器都被觸發,因為這可能會發送歡迎郵件,等等。
Laravel提供可一個方便的 expectsEvents方法來驗證期望的事件被觸發,但同時阻止該事件的其它處理器運行:
expectsEvents(App\Events\UserRegistered::class); // 測試使用者註冊代碼... }}
可以使用 doesntExpectEvents方法來驗證給定事件沒有被觸發:
expectsEvents(App\Events\PodcastWasPurchased::class); $this->doesntExpectEvents(App\Events\PaymentWasDeclined::class); // Test purchasing podcast... }}
如果你想要阻止所有事件運行,可以使用 withoutEvents方法:
withoutEvents(); // 測試使用者註冊代碼... }}
4.2 類比隊列任務
有時候,你可能想要在請求時簡單測試控制器分發的指定任務,這允許你孤立的測試路由/控制器——將其從任務邏輯中分離出去,當然,接下來你可以在一個獨立測試類別中測試工作本身。
Laravel提供了一個方便的 expectsJobs方法來驗證期望的任務被分發,但該任務本身不會被測試:
expectsJobs(App\Jobs\PurchasePodcast::class); // 測試購買播客代碼... }}
注意:這個方法只檢查通過 DispatchesJobstrait分發方法分發的任務,並不檢查直接通過 Queue::push分發的任務。
4.3 類比門面
測試的時候,你可能經常想要類比Laravel門面的調用,例如,看看下面的控制器動作:
我們可以通過使用 shouldReceive方法類比 Cache門面的調用,該方法返回一個 Mockery類比的執行個體,由於門面通過Laravel服務容器解析和管理,它們比通常的靜態類更具有可測試性。例如,我們來類比 Cache門面的調用:
once() ->with('key') ->andReturn('value'); $this->visit('/users')->see('value'); }}
注意:不要類比 Request門面,取而代之地,在測試時傳遞輸入到HTTP協助函數如 call和 post。