Laravel植根于測試,實際上,內(nèi)置使用PHPUnit對測試提供支持是即開即用的,并且phpunit.xml
文件已經(jīng)為應(yīng)用設(shè)置好了??蚣苓€提供了方便的幫助方法允許你對應(yīng)用進(jìn)行富有表現(xiàn)力的測試。
tests
目錄中提供了一個ExampleTest.php
文件,安裝完新的Laravel應(yīng)用后,只需簡單在命令行運行phpunit
來運行測試。
運行測試的時候,Laravel自動設(shè)置配置環(huán)境為testing
。Laravel在測試時自動配置session和cache驅(qū)動為數(shù)組驅(qū)動,這意味著測試時不會持久化存儲session和cache。
如果需要的話你可以自由創(chuàng)建其它測試環(huán)境配置。testing
環(huán)境變量可以在phpunit.xml
文件中配置。
要創(chuàng)建一個測試用例,只需簡單在tests
目錄創(chuàng)建一個新的測試文件,測試類應(yīng)該繼承TestCase
,然后你可以使用PHPUnit定義測試方法。要運行測試,簡單從終端執(zhí)行phpunit
命令即可:
<?php
class FooTest extends TestCase{
public function testSomethingIsTrue()
{
$this->assertTrue(true);
}
}
注意:如果你在測試類中定義自己的
setUp
方法,確保在其中調(diào)用parent::setUp
。
Laravel為生成HTTP請求、測試輸出、以及填充表單提供了平滑的API。舉個例子,我們看下tests
目錄下包含的ExampleTest.php
文件:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
/**
* 基本功能測試示例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5')
->dontSee('Rails');
}
}
visit
方法生成了一個GET請求,see
方法對我們從應(yīng)用返回響應(yīng)中應(yīng)該看到的給定文本進(jìn)行斷言。dontSee
方法對給定文本沒有從應(yīng)用響應(yīng)中返回進(jìn)行斷言。在Laravel中這是最基本的有效應(yīng)用測試。
當(dāng)然,除了對響應(yīng)文本進(jìn)行斷言之外還有做更多測試,讓我們看一些點擊鏈接和填充表單的例子:
在本測試中,我們將為應(yīng)用生成請求,在返回的響應(yīng)中“點擊”鏈接,然后對訪問URI進(jìn)行斷言。例如,假定響應(yīng)中有一個“關(guān)于我們”的鏈接:
<a href="/about-us">About Us</a>
現(xiàn)在,讓我們編寫一個測試點擊鏈接并斷言用戶訪問頁面是否正確:
public function testBasicExample(){
$this->visit('/')
->click('About Us')
->seePageIs('/about-us');
}
Laravel還為處理表單提供了多個方法。type
,?select
,?check
,?attach
, 和press
方法允許你與所有表單輸入進(jìn)行交互。例如,我們假設(shè)這個表單存在于應(yīng)用注冊頁面:
<form action="/register" method="POST">
{!! csrf_field() !!}
<div>
Name: <input type="text" name="name">
</div>
<div>
<input type="checkbox" value="yes" name="terms"> Accept Terms
</div>
<div>
<input type="submit" value="Register">
</div>
</form>
我們可以編寫測試完成表單并檢查結(jié)果:
public function testNewUserRegistration(){
$this->visit('/register')
->type('Taylor', 'name')
->check('terms')
->press('Register')
->seePageIs('/dashboard');
}
當(dāng)然,如果你的表單包含其他輸入比如單選按鈕或下拉列表,也可以輕松填寫這些字段類型。這里是所有表單操作方法列表:
方法 | 描述 |
---|---|
$this->type($text, $elementName) |
“Type” 文本到給定字段 |
$this->select($value, $elementName) |
“Select” 單選框或下拉列表 |
$this->check($elementName) |
“Check” 復(fù)選框 |
$this->attach($pathToFile, $elementName) |
“Attach” 文件到表單 |
$this->press($buttonTextOrElementName) |
“Press” ?給定文本或name的按鈕 |
如果表單包含file
輸入類型,可以使用attach
方法添加文件到表單:
public function testPhotoCanBeUploaded(){
$this->visit('/upload')
->name('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}
Laravel還提供多個幫助函數(shù)用于測試JSON API及其響應(yīng)。例如,get
,?post
,?put
,?patch
, 和?delete
方法用于通過多種HTTP請求方式發(fā)出請求。你還可以輕松傳遞數(shù)據(jù)和頭到這些方法。作為開始,我們編寫測試來生成POST請求到/user
并斷言返回的數(shù)據(jù)是否是JSON格式:
<?php
class ExampleTest extends TestCase{
/**
* 基本功能測試示例
*
* @return void
*/
public function testBasicExample()
{
$this->post('/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
}
seeJson
方法將給定數(shù)組轉(zhuǎn)化為JSON,然后驗證應(yīng)用返回的整個JSON響應(yīng)中的JSON片段。因此,如果在JSON響應(yīng)中有其他屬性,只要給定片段存在的話測試依然會通過。
如果你想要驗證給定數(shù)組和應(yīng)用返回的JSON能夠精確匹配,使用seeJsonEquals
方法:
<?php
class ExampleTest extends TestCase{
/**
* 基本功能測試示例
*
* @return void
*/
public function testBasicExample()
{
$this->post('/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}
Laravel提供個多個幫助函數(shù)在測試期間處理session,首先,可以使用withSession
方法設(shè)置session值到給定數(shù)組。這通常在測試請求前獲取session數(shù)據(jù)時很有用:
<?php
class ExampleTest extends TestCase{
public function testApplication()
{
$this->withSession(['foo' => 'bar'])
->visit('/');
}
}
當(dāng)然,session的通常用于操作用戶狀態(tài),例如認(rèn)證用戶。幫助函數(shù)actingAs
?提供了認(rèn)證給定用戶為當(dāng)前用戶的簡單方法,例如,我們使用模型工廠生成和認(rèn)證用戶:
<?php
class ExampleTest extends TestCase{
public function testApplication()
{
$user = factory('App\User')->create();
$this->actingAs($user)
->withSession(['foo' => 'bar'])
->visit('/')
->see('Hello, '.$user->name);
}
}
測試應(yīng)用時,為某些測試禁止中間件很方便。這種機(jī)制允許你將路由和控制器與中間件孤立開來做測試,Laravel包含了一個簡單的WithoutMiddleware
?trait,可以使用該trait自動在測試類中禁止所有中間件:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
use WithoutMiddleware;
//
}
如果你只想在某些方法中禁止中間件,可以在測試方法中調(diào)用withoutMiddleware
方法:
<?php
class ExampleTest extends TestCase{
/**
* 基本功能測試示例
*
* @return void
*/
public function testBasicExample()
{
$this->withoutMiddleware();
$this->visit('/')
->see('Laravel 5');
}
}
如果你想要在應(yīng)用中生成自定義HTTP請求并獲取完整的Illuminate\Http\Response
對象,可以使用call
方法:
public function testApplication(){
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
如果你要生成POST
,?PUT
, 或者?PATCH
請求可以在請求中傳入輸入數(shù)據(jù)數(shù)組,在路由或控制器中可以通過Request實例訪問請求數(shù)據(jù):
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
Laravel還提供了多種有用的工具讓測試數(shù)據(jù)庫驅(qū)動的應(yīng)用更加簡單。首先,你可以使用幫助函數(shù)seeInDatabase
來斷言數(shù)據(jù)庫中的數(shù)據(jù)是否和給定數(shù)據(jù)集合匹配。例如,如果你想要通過email值為sally@example.com
的條件去數(shù)據(jù)表users
查詢是否存在該記錄 ,我們可以這樣做:
public function testDatabase(){
// 調(diào)用應(yīng)用...
$this->seeInDatabase('users', ['email' => 'sally@foo.com']);
}
每次測試后重置數(shù)據(jù)庫通常很有用,這樣的話上次測試的數(shù)據(jù)不會影響下一次測試。
一種方式是每次測試后回滾數(shù)據(jù)庫并在下次測試前重新遷移。Laravel提供了一個簡單的DatabaseMigrations
?trait來自動為你處理。在測試類上簡單使用該trait如下:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
use DatabaseMigrations;
/**
* 基本功能測試示例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
另一種方式是將每一個測試用例包裹到一個數(shù)據(jù)庫事務(wù)中,Laravel提供了方便的?DatabaseTransactions
?trait自動為你處理:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase{
use DatabaseTransactions;
/**
* 基本功能測試示例
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
測試時,通常需要在執(zhí)行測試前插入新數(shù)據(jù)到數(shù)據(jù)庫。在創(chuàng)建測試數(shù)據(jù)時,Laravel允許你使用”factories”為每個Eloquent模型定義默認(rèn)的屬性值集合,而不用手動為每一列指定值。作為開始,我們看一下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),
];
});
在閉包中,作為工廠定義,我們返回該模型上所有屬性默認(rèn)測試值。該閉包接收PHP庫Faker實例,從而允許你方便地為測試生成多種類型的隨機(jī)數(shù)據(jù)。
當(dāng)然,你可以添加更多工廠到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
方法獲取基本屬性而不用重復(fù)基本用戶工廠中的所有屬性,獲取這些屬性后,只需將你要求的額外值增補(bǔ)進(jìn)去即可:
$factory->defineAs(App\User::class, 'admin', function ($faker) use ($factory) {
$user = $factory->raw(App\User::class);
return array_merge($user, ['admin' => true]);
});
定義好工廠后,可以在測試或數(shù)據(jù)庫填充文件中通過全局的factory
方法使用它們來生成模型實例,所以,讓我們看一些生成模型的例子,首先,我們使用make
方法,該方法創(chuàng)建模型但不將其保存到數(shù)據(jù)庫:
public function testDatabase(){
$user = factory(App\User::class)->make();
// 用戶模型測試...
}
如果你想要覆蓋模型的一些默認(rèn)值,可以傳遞數(shù)組值到make
方法。只有指定值被替換,其他數(shù)據(jù)保持不變:
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
還可以創(chuàng)建多個模型集合或者創(chuàng)建給定類型的集合:
// 創(chuàng)建3個 App\User 實例...
$users = factory(App\User::class, 3)->make();
// 創(chuàng)建1個 App\User "admin" 實例...
$user = factory(App\User::class, 'admin')->make();
// 創(chuàng)建3個 App\User "admin" 實例...
$users = factory(App\User::class, 'admin', 3)->make();
create
方法不僅能創(chuàng)建模型實例,還可以使用Eloquent的save
方法將它們保存到數(shù)據(jù)庫:
public function testDatabase(){
$user = factory(App\User::class)->create();
//用戶模型測試...
}
你仍然可以通過傳遞數(shù)組到create方法覆蓋模型上的屬性:
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
你甚至可以持久化多個模型到數(shù)據(jù)庫。在本例中,我們添加一個關(guān)聯(lián)到創(chuàng)建的模型,使用create
方法創(chuàng)建多個模型的時候,會返回一個Eloquent集合實例,從而允許你使用集合提供的所有便利方法,例如each
:
$users = factory(App\User::class, 3)
->create()
->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
如果你在重度使用Laravel的時間系統(tǒng),可能想要在測試時模擬特定事件。例如,如果你在測試用戶注冊,你可能不想所有UserRegistered
的時間處理器都被觸發(fā),因為這可能會發(fā)送歡迎郵件,等等。
Laravel提供可一個方便的expectsEvents
方法來驗證期望的事件被觸發(fā),但同時阻止該事件的其它處理器運行:
<?php
class ExampleTest extends TestCase{
public function testUserRegistration()
{
$this->expectsEvents(App\Events\UserRegistered::class);
// 測試用戶注冊代碼...
}
}
如果你想要阻止所有事件運行,可以使用withoutEvents
方法:
<?php
class ExampleTest extends TestCase{
public function testUserRegistration()
{
$this->withoutEvents();
// 測試用戶注冊代碼...
}
}
有時候,你可能想要在請求時簡單測試控制器分發(fā)的指定任務(wù),這允許你孤立的測試路由/控制器——將其從任務(wù)邏輯中分離出去,當(dāng)然,接下來你可以在一個獨立測試類中測試任務(wù)本身。
Laravel提供了一個方便的expectsJobs
方法來驗證期望的任務(wù)被分發(fā),但該任務(wù)本身不會被測試:
<?php
class ExampleTest extends TestCase{
public function testPurchasePodcast()
{
$this->expectsJobs(App\Jobs\PurchasePodcast::class);
// 測試購買播客代碼...
}
}
注意:這個方法只檢查通過
DispatchesJobs
?trait分發(fā)方法分發(fā)的任務(wù),并不檢查直接通過Queue::push
分發(fā)的任務(wù)。
測試的時候,你可能經(jīng)常想要模擬Laravel門面的調(diào)用,例如,看看下面的控制器動作:
<?php
namespace App\Http\Controllers;
use Cache;
use Illuminate\Routing\Controller;
class UserController extends Controller{
/**
* 顯示應(yīng)用用戶列表
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
我們可以通過使用shouldReceive
方法模擬Cache
門面的調(diào)用,該方法返回一個Mockery模擬的實例,由于門面通過Laravel服務(wù)容器解析和管理,它們比通常的靜態(tài)類更具有可測試性。例如,我們來模擬Cache
門面的調(diào)用:
<?php
class FooTest extends TestCase{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
}
注意:不要模擬
Request
門面,取而代之地,在測試時傳遞輸入到HTTP幫助函數(shù)如call
和post
。
更多建議: