前陣子看了點Laravel源碼尖飞,越看越亂屏富,網(wǎng)上大部分中文文檔都是直譯奉瘤,比較生澀難懂泌射,還是決定看英文文檔順便就我的理解做下翻譯整理記錄下來
簡介
Laravel構(gòu)建的時候就帶上測試。實際上鞠苟,內(nèi)含支持PHPUnit的測試乞榨,開箱即用,同時已經(jīng)為你應用創(chuàng)建了phpunit.xml
文件偶妖〗啵框架給你提供了方便的幫助方法,可以讓你形象地測試你應用趾访。
tests
文件夾 提供了 ExampleText.php
文件态秧。安裝完Laravel應用,只要在命令行執(zhí)行vendor/bin
下的 phpunit
就能運行你的測試
測試環(huán)境
當你運行測試扼鞋,Laravel會自動為測試配置環(huán)境申鱼。測試的時候Laravel自動配置session和緩存到你的數(shù)組驅(qū)動,意味著測試的時候不會持久化session和緩存數(shù)據(jù)云头。
需要的話捐友,你可以自由的創(chuàng)建其他測試環(huán)境。測試環(huán)境的參數(shù)可以在phpunit.xml
文件里配置溃槐,但要在運行測試前確保你用config:clear
Artisan命令清理的配置緩存匣砖。
定義&運行測試
用make:test
Artisan命令創(chuàng)建一個新的測試案例:
php artisan make:test UserTest
這個命令會在tests
文件夾下創(chuàng)建一個新的UserTest
。然后你就可以像平時用PHPUnit一樣定義你的測試方法昏滴。只要執(zhí)行phpunit
命令就可以運行測試:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class UserTest extends TestCase
{
/**
* A basic test example.
*
* @return void
*/
public function testExample()
{
$this->assertTrue(true);
}
}
注意:如果在測試類定義你自己的
setUp
方法猴鲫,確保使用parent:setUp
應用測試
Laravel提供了非常流暢的API,可以讓你向引用發(fā)送Http請求,檢測輸出谣殊,甚至填充表單拂共。
例如,看下ExamleTest.php
:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5')
->dontSee('Rails');
}
}
visit
方法向應用發(fā)送了一個Get
請求姻几。see
方法確保在返回的消息中可以看到給定文本宜狐。dontSee
方法確保返回消息中沒有給定文本。這是Laravel可用的最基本的應用測試蛇捌。
和應用交互
當然抚恒,比起確保文本出現(xiàn)在給定回復,你可以做更多络拌。讓我們來看些點擊鏈接和填充表單的例子:
點擊鏈接
在這個測試中柑爸,我們將對應用發(fā)起請求,在返回的響應中“點擊”鏈接盒音,然后確保登入指定URL表鳍。例如,我們假設在返回的響應 有一個文本為“About Us”的鏈接:
<a href = "/about-us"> About Us</a>
現(xiàn)在祥诽,讓我們來寫一個測試點擊鏈接確保用戶打開正確的頁面:
public function testBasicExample()
{
$this->visit('/')
->click('About Us')
->seePageIs('/about-us');
}
處理表單
Laravel也為測試表單提供了多重方法譬圣。type
,select
,check
,attack
,和press
方法允許你和所有的表單輸入框做交互。例如雄坪,讓我們想象一下應用的注冊頁面上有這樣一個表單:
<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');
}
當然如果表單包含其他輸入比如單選按鈕和下拉菜單厘熟,你也可以輕松的填充這些字段類型。這里列出每個表單的操作方法:
方法 | 描述 |
---|---|
$this->type($text, $elementName) |
輸入文本 |
$this->select($value, $elementName) |
選擇單選按鈕和下拉框 |
$this->check($elementName) |
多選框選擇 |
$this->uncheck($elementName) |
多選框取消選擇 |
$this->attach($pathToFile, $elementName) |
添加附件 |
$this->press($buttonTextOrElementName) |
點擊按鈕 |
處理附件
如果表單包含file
輸入類型维哈,你可以用attach
方法關(guān)聯(lián)
public function testPhotoCanBeUploaded()
{
$this->visit('/upload')
->type('File Name', 'name')
->attach($absolutePathToFile, 'photo')
->press('Upload')
->see('Upload Successful!');
}
測試 JSON API
Laravel也為測試JSON API和它們的響應提供了許多幫助绳姨。例如,get
,post
,put
,patch
和delete
方法用來解決各種HTTP請求阔挠。你也可以輕松用這些方法傳遞數(shù)據(jù)和頭文件飘庄。首先,讓我們寫一個測試购撼,對/user
發(fā)送一個POST
請求然后確保給定的數(shù)組在返回的Json格式中:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJson([
'created' => true,
]);
}
}
seeJson
方法把給定數(shù)組轉(zhuǎn)化成JSON,然后核實這個JSON片段是否在返回的JSON響應中出現(xiàn)跪削。所以,就算返回的JSON里有其他的屬性迂求,只要給定片段存在依然可以通過測試碾盐。
核實JSON精準匹配
如果你像核實給定數(shù)組完全匹配應用返回的JSON,你可以用seeJsonEqual
方法:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->json('POST', '/user', ['name' => 'Sally'])
->seeJsonEquals([
'created' => true,
]);
}
}
驗證JSON結(jié)構(gòu)匹配
你可以驗證返回JSON是否特定結(jié)構(gòu)揩局。為此毫玖,你可以使用seeJsonStructure
方法同時傳遞一系列嵌套關(guān)鍵字:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->get('/user/1')
->seeJsonStructure([
'name',
'pet' => [
'name', 'age'
]
]);
}
}
上面的例子期望收到一個name
和一個包含name
和age
的嵌套對象pet
。只要額外關(guān)鍵字帶響應中存在seeJsonStructure
就不會失敗凌盯。比如付枫,就算pet
有一個weight
屬性測試也會通過。
你可以使用*
來確保返回的JSON結(jié)構(gòu)里每一個列表項都至少包含設置的屬性:
<?php
class ExampleTest extends TestCase
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
// Assert that each user in the list has at least an id, name and email attribute.
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email'
]
]);
}
}
你可以在嵌套中使用*
十气,這樣的話励背,你可以確保每個用戶的數(shù)據(jù)里都包含給定的屬性集同是每個pet
屬性都包含給定屬性集:
$this->get('/users')
->seeJsonStructure([
'*' => [
'id', 'name', 'email', 'pets' => [
'*' => [
'name', 'age'
]
]
]
]);
Session/認證
Laravel為測試期間用session提供了一些幫助。首先砸西,你可以用withSession
方法把session
的數(shù)據(jù)設置成指定數(shù)組叶眉。這個方法對請求前加載session很有用:
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$this->withSession(['foo' => 'bar'])
->visit('/');
}
}
當然,seesion常見的應用就是保存用戶狀態(tài)芹枷,比如認證用戶衅疙。actingAs
方法提供了一個認證給定用戶為當前用戶的簡便方法。比如鸳慈,我們可以用model factory
生成和認證一個用戶:
<?php
class ExampleTest extends TestCase
{
public function testApplication()
{
$user = factory(App\User::class)->create();
$this->actingAs($user)
->withSession(['foo' => 'bar'])
->visit('/')
->see('Hello, '.$user->name);
}
}
你也可以指定用哪個guard
來認證給定用戶饱溢,通過給actingAs
方法的第二個參數(shù)傳遞一個guard
名字:
$this->actingAs($user, 'backend')
中間件不可用
在測試你的應用的時候,你會發(fā)現(xiàn)你可以很方便的在某些測試中讓中間件不可用走芋。這將使你可以在隔離中間件的情況下測試你的路由和控制器绩郎。Laravel包含一個WithoutMiddlware
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
{
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->withoutMiddleware();
$this->visit('/')
->see('Laravel 5');
}
}
自定義HTTP請求
如果你像發(fā)送自定義HTTP請求來獲取完整的Illuminate\Http\Response
對象溉仑,你可以使用call
方法:
public function testApplication()
{
$response = $this->call('GET', '/');
$this->assertEquals(200, $response->status());
}
如果你創(chuàng)建一個POST
,PUT
或者PATCH
請求状植,你可能需要傳送一個輸入數(shù)據(jù)的數(shù)組浊竟。當然,通過Request實例
這些數(shù)據(jù)將在你的路由和控制器內(nèi)可用:
$response = $this->call('POST', '/user', ['name' => 'Taylor']);
PHPUnit 斷言
Laravel 為PHPUnit
提供了多個額外的斷言方法:
方法 | 描述 |
---|---|
->assertResponseOk(); |
確認客戶端響應一個OK狀態(tài) |
->assertResponseStatus($code); |
確認客戶端響應一個指定代碼 |
->assertViewHas($key, $value = null); |
確認客戶端響應視圖里有給定綁定數(shù)據(jù)片段 |
->assertViewHasAll(array $bindings); |
確認客戶端響應視圖有給定綁定數(shù)據(jù)隊列 |
->assertViewMissing($key); |
確認客戶端響應視圖缺失一對綁定數(shù)據(jù) |
->assertRedirectedTo($uri, $with = []); |
確認客戶端是否重定向到指定URL |
->assertRedirectedToRoute($name, $parameters = [], $with = []); |
確認客戶端是否重定向到指定路由 |
->assertRedirectedToAction($name, $parameters = [], $with = []); |
確認客戶端是否重定向到指定動作 |
->assertSessionHas($key, $value = null); |
確認Session里有給定值 |
->assertSessionHasAll(array $bindings); |
確認Session中有給定值集合 |
->assertSessionHasErrors($bindings = [], $format = null); |
確認Session有錯誤綁定 |
->assertHasOldInput(); |
確認Session中有舊的輸入 |
->assertSessionMissing($key); |
確認Session中缺失指定關(guān)鍵字 |
數(shù)據(jù)庫操作
Laravel提供了各種各樣有用的工具來簡化測試我們的數(shù)據(jù)庫驅(qū)動應用津畸。首先振定,你可以用seeInDatabase
來確認數(shù)據(jù)庫里是存在符合你條件的數(shù)據(jù)。例如肉拓,如果你像驗證在user
表中有條數(shù)據(jù)它email
值為sally@example.com
后频,你可以這樣寫:
public function testDatabase()
{
// Make call to application...
$this->seeInDatabase('users', ['email' => 'sally@example.com']);
}
當然,seeInDatabase
方法和其他類似的幫助方法都是為了方便帝簇。你可以自由使用任何PHPUnit的內(nèi)建斷言方法來支持你的測試徘郭。
測試后重置數(shù)據(jù)庫
在測試后重置數(shù)據(jù)庫是很有用的,這讓后續(xù)測試不會受到前面測試的數(shù)據(jù)影響丧肴。
使用Migration
一個選擇就是每次測試完都回滾數(shù)據(jù)庫然后在下次測試前移植過去残揉。Laravel提供了DatabaseMigrations
trait來自動進行這些操作:
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class ExampleTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
使用會話
另一個選擇就是把每個測試案列包括在一個數(shù)據(jù)庫會話中。同樣的芋浮,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;
/**
* A basic functional test example.
*
* @return void
*/
public function testBasicExample()
{
$this->visit('/')
->see('Laravel 5');
}
}
注意:這個trait只是把默認的數(shù)據(jù)庫連接包裹在會話中
模塊工廠
在指定測試前抱环,一般都需要插入一些記錄到數(shù)據(jù)庫中。Laravel允許使用"工廠"為你所有的Eloquent models
定義一個屬性集合纸巷,你就不用在你創(chuàng)建測試數(shù)據(jù)的時候手動指定每一列的值了镇草。首先,讓我們看一下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),
];
});
在factory定義的閉包中,你可以返回模塊上的所有屬性的測試值存哲。這個閉包會接受一個Faker PHP Library
實例因宇,它允許你很方便的生成各種隨機的數(shù)據(jù)來測試。
當然祟偷,你可以自由地在ModelFactory.php
中添加自己的額外工廠察滑。你也可以為每個model添加另外的工廠文件來更好的組織。比如修肠,你可以在你的database/factories
目錄下創(chuàng)建UserFactory.php
和CommentFactory.php
文件贺辰。
多樣工廠類型
有時候你像為同一個Eloquent model類創(chuàng)建多個工程溉浙。比如除了普通用戶你還想為管理員用戶添加工程筷屡。你可以用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
方法在你的測試或數(shù)據(jù)庫seed文件中使用它們來生成model實例吃靠。讓我們看一些創(chuàng)建model的例子蒋川。首先,我們用make
方法撩笆,它會創(chuàng)建model但不會存入數(shù)據(jù)庫:
public function testDatabase()
{
$user = factory(App\User::class)->make();
// Use model in tests...
}
如果你想重寫一些你model中的默認值,你可以給make
方法傳遞一個數(shù)組缸浦。只有指定的值會被替換夕冲,其他的值都會保留你在工廠中定義的默認值:
$user = factory(App\User::class)->make([
'name' => 'Abigail',
]);
你也可以創(chuàng)建一個model集合或者創(chuàng)建一個給定類型的model:
// Create three App\User instances...
$users = factory(App\User::class, 3)->make();
// Create an App\User "admin" instance...
$user = factory(App\User::class, 'admin')->make();
// Create three App\User "admin" instances...
$users = factory(App\User::class, 'admin', 3)->make();
持久化工廠model
create
方法不但創(chuàng)建model實例,還會用Eloquent的save
方法把它們存入數(shù)據(jù)庫:
public function testDatabase()
{
$user = factory(App\User::class)->create();
// Use model in tests...
}
同樣的裂逐,你可以通過給create
方法傳數(shù)組來重寫model屬性:
$user = factory(App\User::class)->create([
'name' => 'Abigail',
]);
為Model添加關(guān)聯(lián)
你或許想保存多個model到數(shù)據(jù)庫歹鱼。在這個例子中,你可以對一個已建model附加關(guān)聯(lián)卜高。當你用create
方法創(chuàng)建多個models弥姻,會返回一個Eloquent collection
實例,它讓你可以用它提供的任何快捷函數(shù)掺涛,比如each
:
$users = factory(App\User::class, 3)
->create()
->each(function ($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
關(guān)聯(lián)和屬性閉包
你可以在工廠定義中用屬性閉包添加關(guān)聯(lián)庭敦。比如,如果你想在創(chuàng)建Post
的時候創(chuàng)建一個User
,你可以這樣做:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
}
];
});
這些閉包還能接收工廠的屬性數(shù)組:
$factory->define(App\Post::class, function ($faker) {
return [
'title' => $faker->title,
'content' => $faker->paragraph,
'user_id' => function () {
return factory(App\User::class)->create()->id;
},
'user_type' => function (array $post) {
return App\User::find($post['user_id'])->type;
}
];
});
模仿
模仿事件
如果你在大量使用Laravel的事件系統(tǒng)薪缆,你可能希望在測試中有一個讓事件安靜下來或者模擬它秧廉。比如,如果你在測試用戶注冊拣帽,你可能不希望所有的UserRegistered
事件操作被執(zhí)行疼电,因為它們會發(fā)送一個welcome
電子郵件等等。
Laravel提供了一個expectsEvents
方法來驗證預計的時間被執(zhí)行减拭,同時阻止任何這些時間的操作被執(zhí)行:
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->expectsEvents(App\Events\UserRegistered::class);
// Test user registration...
}
}
你可以用doesntExpectEvents
方法來驗證給定事件沒有被觸發(fā):
<?php
class ExampleTest extends TestCase
{
public function testPodcastPurchase()
{
$this->expectsEvents(App\Events\PodcastWasPurchased::class);
$this->doesntExpectEvents(App\Events\PaymentWasDeclined::class);
// Test purchasing podcast...
}
}
如果你像阻止所有的事件操作蔽豺,你可以用withoutEvents
方法:
<?php
class ExampleTest extends TestCase
{
public function testUserRegistration()
{
$this->withoutEvents();
// Test user registration code...
}
}
模擬工作
有時候,當你發(fā)送請求給應用的時候你想測試特定工作是否被控制器分派下來拧粪。這允許你隔離工作邏輯來測試路由或者控制器修陡。當然,你可以在獨立的測試類里測試工作既们。
Laravel提供了expectsJobs
方法來驗證預期的工作有沒有分派下來濒析,但不會執(zhí)行工作:
<?php
class ExampleTest extends TestCase
{
public function testPurchasePodcast()
{
$this->expectsJobs(App\Jobs\PurchasePodcast::class);
// Test purchase podcast code...
}
}
注意:這個方法只會檢索通過
DispatchesJobs
trait或者dispatch
幫助函數(shù)分派下來的方法。它不會檢索由Queue::push
直接發(fā)送下來的工作啥纸。
模擬門面
在測試中号杏,你可能經(jīng)常希望模擬調(diào)用一個Laravel門面,例如,看一下下面的控制器動作:
<?php
namespace App\Http\Controllers;
use Cache;
class UserController extends Controller
{
/**
* Show a list of all users of the application.
*
* @return Response
*/
public function index()
{
$value = Cache::get('key');
//
}
}
你可以使用shouldReceive
方法模擬調(diào)用Cache
門面盾致,它會返回一個Mockery
實例主经。由于門面實際上由Laravel的服務容器處理和管理的,它們會比典型的靜態(tài)類更容易測試庭惜。例如罩驻,讓我們模擬條用Cache
門面:
<?php
class FooTest extends TestCase
{
public function testGetIndex()
{
Cache::shouldReceive('get')
->once()
->with('key')
->andReturn('value');
$this->visit('/users')->see('value');
}
}
注意:在你運行測試的時候,不要去模擬
Request
門面护赊,你應該把你想要的輸入傳入HTTP幫助方法比如call
和post