基于 TDD 模式編寫 Vue 評論組件(下):Axios 請求后端接口測試

在本篇教程中,學(xué)院君將以評論創(chuàng)建接口為例,演示如何在 Vue 組件中為通過 Axios 調(diào)用后端接口編寫測試用例。
一、準(zhǔn)備工作
后端接口數(shù)據(jù)格式統(tǒng)一
開始之前,我們先將后端評論創(chuàng)建接口返回?cái)?shù)據(jù)格式進(jìn)行統(tǒng)一,目前表單驗(yàn)證失敗、評論創(chuàng)建成功、數(shù)據(jù)庫異常返回的接口數(shù)據(jù)格式都是不一樣的,這會給前端接口調(diào)用和前后端接口聯(lián)調(diào)造成諸多不便。
這里學(xué)院君在 app/Http/Controllers 目錄下新建了一個(gè) DataTransformer Trait 來簡單處理控制器動(dòng)作返回的接口數(shù)據(jù)格式:
<?php
namespace App\Http\Controllers;
trait DataTransformer
{
public function generateResponseData($success, $message, $data = null)
{
return [
'success' => $success, // 是否成功
'message' => $message, // 提示消息
'data' => $data // 如果成功則返回成功數(shù)據(jù),否則顯示錯(cuò)誤明細(xì)(驗(yàn)證表單時(shí)使用)
];
}
}
如代碼所示,現(xiàn)在將所有后端接口返回的數(shù)據(jù)統(tǒng)一為三個(gè)字段,分別標(biāo)識是否成功、消息文案和數(shù)據(jù)明細(xì)。然后我們將 CommentController 的 store 方法改造如下:
<?php
namespace App\Http\Controllers;
use App\Models\Comment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class CommentController extends Controller
{
use DataTransformer;
...
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'content' => 'required'
]);
if ($validator->fails()) {
return response()->json($this->generateResponseData(false, '表單驗(yàn)證失敗', $validator->errors()))
->setStatusCode(422);
}
$data = $validator->getData();
try {
$comment = new Comment($data);
if ($comment->save()) {
return $this->generateResponseData(true, '評論保存成功', $comment);
}
Log::warning('未能保存評論數(shù)據(jù)到數(shù)據(jù)庫: ' . json_encode($data));
return response()->json($this->generateResponseData(false, '評論保存失敗'))
->setStatusCode(400);
} catch (\Throwable $ex) {
Log::error('保存評論數(shù)據(jù)到數(shù)據(jù)庫出現(xiàn)異常:' . $ex->getMessage());
return response()->json($this->generateResponseData(false, '保存數(shù)據(jù)異常'))
->setStatusCode(500);
}
}
現(xiàn)在創(chuàng)建評論接口不管是字段規(guī)則驗(yàn)證、數(shù)據(jù)保存成功還是數(shù)據(jù)庫處理異常,都會返回統(tǒng)一的 JSON 格式數(shù)據(jù),并且不同的響應(yīng)對應(yīng)不同的狀態(tài)碼,從而方便通過響應(yīng)狀態(tài)碼快速識別響應(yīng)結(jié)果:
{
"success": boolean,
"message": string,
"data": object|null
}
通過 cURL 命令進(jìn)行驗(yàn)證
為了方便在命令行測試后端接口響應(yīng),可以在 app/Http/Middleware/VerifyCsrfToken.php 中間件中取消該路由的 CSRF 防護(hù):
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
"comments"
];
}
當(dāng)然,如果該路由注冊在
routes/api.php中,則不需要這么做,因?yàn)樗?API 路由都沒有應(yīng)用這個(gè)中間件。
接下來,就可以通過 curl 命令在終端測試修改后的評論創(chuàng)建接口返回的數(shù)據(jù)格式了,我們先看表單驗(yàn)證失敗的響應(yīng):
curl -i -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"content":""}' http://127.0.0.1:8000/comments

狀態(tài)碼依然是 422,響應(yīng)實(shí)體中包含了 JSON 格式的錯(cuò)誤信息。
再來看評論創(chuàng)建成功的響應(yīng)信息:
curl -i -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"content":"測試評論"}' http://127.0.0.1:8000/comments

響應(yīng)狀態(tài)碼是 200,響應(yīng)實(shí)體中包含了 JSON 格式的響應(yīng)數(shù)據(jù),評論信息位于 data 屬性中。
最后再來看看數(shù)據(jù)庫異常情況下的響應(yīng)數(shù)據(jù)(修改數(shù)據(jù)庫連接信息或者在存儲時(shí)將 content 屬性置空即可模擬數(shù)據(jù)庫處理異常):

響應(yīng)狀態(tài)碼是 500,響應(yīng)實(shí)體中可以通過 message 字段獲取錯(cuò)誤信息。
二、前端測試用例編寫
調(diào)整評論組件代碼
對后端接口有了總體的認(rèn)識之后,接下來我們在 CommentComponent 組件中調(diào)整 addNewComment 方法,將評論列表數(shù)據(jù)添加邏輯改為請求后端評論創(chuàng)建接口獲?。?/p>
addNewComment() {
axios.post('comments', {
content: this.content
}).then(resp => {
let comment = resp.data.data;
this.comments.push(comment);
this.content = '';
}).catch(error => {
console.log(error);
});
},
如果你在終端運(yùn)行著 npm run watch-test,此時(shí)會收到報(bào)錯(cuò)信息:

這是因?yàn)橹笆峭教砑釉u論數(shù)據(jù)到評論列表屬性的,現(xiàn)在改為通過 Axios 請求后端評論創(chuàng)建接口設(shè)置,變成了異步模式,而且這個(gè)異步模式不同于之前 Vue 組件 DOM 的異步刷新機(jī)制,是一個(gè)完全不可控的行為(比如網(wǎng)絡(luò)異常),因此也不能簡單通過 Vue.nextTick 回調(diào)進(jìn)行測試。
要測試這種通過對外部接口的調(diào)用進(jìn)而實(shí)現(xiàn) Vue 組件本身的更新,和后端測試外部 HTTP 接口一樣,需要通過攔截 Axios 請求返回偽造的響應(yīng)數(shù)據(jù)來實(shí)現(xiàn),只要返回的數(shù)據(jù)格式滿足上面后端接口約定的 JSON 數(shù)據(jù)格式即可。
安裝請求測試偽造庫
JavaScript 生態(tài)中有很多類似的請求測試偽造庫,比如 Axios 官方提供的針對 Axios 請求進(jìn)行模擬和測試的 moxios,以及獨(dú)立的第三方請求測試偽造庫 sinon 等,這里我們主要測試的 Axios 請求,所以使用 moxios 即可。
使用之前,需要先通過 NPM 安裝它:
npm install moxios --save-dev
編寫 Axios 請求測試用例
接下來,就可以基于 Mocha + moxios 編寫 Axios 請求測試用例了,先打開 setup.js,定義全局變量 axios,以便可以在所有測試用例中使用:
...
global.axios = require('axios');
然后打開評論測試文件 comment.spec.js,按照 moxios 官方示例 編寫針對 comments 接口的請求測試用例:
...
import moxios from "moxios"
describe('CommentComponent.vue', () => {
let wrapper;
beforeEach(() => {
moxios.install(axios)
wrapper = mount(CommentComponent, {
comment: {
content: '',
voted: false
},
comments: []
})
})
afterEach(() => {
moxios.uninstall(axios)
})
...
it('click submit button will render comment in comments list', function (done) {
// Given
expect(wrapper.find('ul.comments').isVisible()).toBe(false);
let comment = '大家好,我是學(xué)院君。';
wrapper.find('textarea[name=content]').element.value = comment;
wrapper.find('textarea[name=content]').trigger('input');
// When
wrapper.find('button[type=submit]').trigger('submit');
// Then
moxios.wait(() => {
// 攔截最近一次請求
let request = moxios.requests.mostRecent();
// 針對該請求返回偽造的響應(yīng)數(shù)據(jù)(請求不會到達(dá)服務(wù)器直接返回)
request.respondWith({
status: 200,
response: {
success: true,
message: '評論保存成功',
data: {
id: 1,
content: comment,
voted: false
}
}
}).then(() => {
expect(wrapper.vm.comments.length).toEqual(1);
expect(wrapper.vm.comments[0].content).toContain(comment);
wrapper.vm.$nextTick(() => {
// 需要將這兩個(gè)斷言放到 Vue.nextTick 中執(zhí)行,因?yàn)樗鼈冃枰?nbsp;DOM 刷新之后才會生效
expect(wrapper.find('ul.comments').isVisible()).toBe(true);
expect(wrapper.find('ul.comments').html()).toContain(comment);
});
done();
})
});
});
...
});
主要調(diào)整在 Then 部分,通過在 moxios.wait 回調(diào)函數(shù)中攔截最近一次請求,然后針對該請求返回偽造的響應(yīng)數(shù)據(jù),最后在 then 回調(diào)中編寫斷言代碼即可。注意到我們在該測試用例回調(diào)中傳入了一個(gè) done,并且在斷言的最后執(zhí)行了 done() 方法,用于標(biāo)識請求處理完成,退出 moxios.wait,這個(gè)邏輯不能省略。
另外,在整個(gè)用例的 beforeEach 和 afterEach 方法中,分別進(jìn)行了 moxios 庫的安裝和卸載工作,在這兩個(gè)操作中必須帶上 axios 實(shí)例,否則測試不會通過。
如果你運(yùn)行了 npm run watch-test,此時(shí)可以看到測試通過,說明我們的 addNewComment 方法調(diào)用沒有問題,接下來,可以與后端接口直接進(jìn)行聯(lián)調(diào)了:

當(dāng)然,學(xué)院君這里只是拋磚引玉,moxios 還有更多功能,比如定義樁請求和響應(yīng),并且支持正則路由匹配(這在測試更新某個(gè)項(xiàng)目或者獲取項(xiàng)目詳情請求時(shí)很有用),你可以查看官方示例代碼進(jìn)行試驗(yàn),非常簡單,這里就不一一演示了。
至此,我們的 Vue 組件測試驅(qū)動(dòng)開發(fā)入門之旅就告一段落了,接下來,我們將正式進(jìn)入單頁面應(yīng)用實(shí)戰(zhàn)之旅。
本系列教程首發(fā)在Laravel學(xué)院(laravelacademy.org),你可以點(diǎn)擊頁面左下角閱讀原文鏈接查看最新更新的教程。
