本文目录导读:

我来详细介绍ThinkPHP项目中如何进行模拟请求和断言测试。
使用PHPUnit进行接口测试
1 安装依赖
composer require phpunit/phpunit --dev composer require guzzlehttp/guzzle --dev
2 创建测试基类
<?php
// tests/TestCase.php
namespace tests;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
protected $app;
protected $httpClient;
protected function setUp(): void
{
parent::setUp();
// 初始化ThinkPHP应用
$this->app = new \think\App();
$this->app->initialize();
// 创建Guzzle客户端
$this->httpClient = new \GuzzleHttp\Client([
'base_uri' => 'http://localhost:8080',
'timeout' => 5.0,
'http_errors' => false
]);
}
/**
* 发送GET请求
*/
protected function get($uri, $headers = [])
{
try {
$response = $this->httpClient->get($uri, [
'headers' => array_merge([
'Accept' => 'application/json',
], $headers)
]);
return $this->parseResponse($response);
} catch (\Exception $e) {
return [
'status' => 500,
'body' => ['message' => $e->getMessage()],
'raw' => $e
];
}
}
/**
* 发送POST请求
*/
protected function post($uri, $data = [], $headers = [])
{
try {
$response = $this->httpClient->post($uri, [
'json' => $data,
'headers' => array_merge([
'Accept' => 'application/json',
], $headers)
]);
return $this->parseResponse($response);
} catch (\Exception $e) {
return [
'status' => 500,
'body' => ['message' => $e->getMessage()],
'raw' => $e
];
}
}
/**
* 解析响应
*/
protected function parseResponse($response)
{
$statusCode = $response->getStatusCode();
$content = $response->getBody()->getContents();
$json = json_decode($content, true);
return [
'status' => $statusCode,
'body' => $json !== null ? $json : ['raw' => $content],
'headers' => $response->getHeaders()
];
}
/**
* 断言响应状态码
*/
protected function assertStatus($response, $expectedStatus)
{
$this->assertEquals($expectedStatus, $response['status'],
"Expected status code {$expectedStatus}, got {$response['status']}");
}
/**
* 断言响应包含指定字段
*/
protected function assertResponseHasKey($response, $key)
{
$this->assertArrayHasKey($key, $response['body'],
"Response does not contain key '{$key}'");
}
/**
* 断言响应等于预期值
*/
protected function assertResponseEquals($response, $expected, $key = null)
{
if ($key !== null) {
$this->assertEquals($expected, $response['body'][$key] ?? null);
} else {
$this->assertEquals($expected, $response['body']);
}
}
}
创建具体的测试类
1 用户接口测试
<?php
// tests/Api/UserTest.php
namespace tests\Api;
use tests\TestCase;
class UserTest extends TestCase
{
protected $token;
/**
* 测试用户登录
* @test
*/
public function testLogin()
{
$response = $this->post('/api/user/login', [
'username' => 'admin',
'password' => '123456'
]);
$this->assertStatus($response, 200);
$this->assertResponseHasKey($response, 'token');
$this->assertResponseHasKey($response, 'user_info');
$this->token = $response['body']['token'];
return $response['body']['token'];
}
/**
* 测试获取用户信息
* @test
* @depends testLogin
*/
public function testGetUserInfo($token)
{
$response = $this->get('/api/user/info', [
'Authorization' => "Bearer {$token}"
]);
$this->assertStatus($response, 200);
$this->assertResponseEquals($response, 'success', 'message');
$this->assertResponseHasKey($response, 'data');
}
/**
* 测试更新用户信息
* @test
*/
public function testUpdateUser()
{
$token = $this->testLogin()['token'] ?? '';
$response = $this->post('/api/user/update', [
'nickname' => '测试用户',
'email' => 'test@example.com'
], [
'Authorization' => "Bearer {$token}"
]);
$this->assertStatus($response, 200);
$this->assertResponseEquals($response, 1, 'code');
}
/**
* 测试用户列表(带token)
* @test
*/
public function testGetUserList()
{
$token = $this->testLogin()['token'] ?? '';
$response = $this->get('/api/user/list?page=1&limit=10', [
'Authorization' => "Bearer {$token}"
]);
$this->assertStatus($response, 200);
$this->assertResponseHasKey($response, 'list');
$this->assertResponseHasKey($response, 'total');
}
}
2 订单接口测试
<?php
// tests/Api/OrderTest.php
namespace tests\Api;
use tests\TestCase;
class OrderTest extends TestCase
{
protected $token;
public function setUp(): void
{
parent::setUp();
// 获取token
$loginResponse = $this->post('/api/user/login', [
'username' => 'admin',
'password' => '123456'
]);
$this->token = $loginResponse['body']['token'] ?? '';
}
/**
* 创建订单
* @test
*/
public function testCreateOrder()
{
$orderData = [
'order_no' => 'ORD' . time(),
'total_amount' => 99.50,
'items' => [
['product_id' => 1, 'quantity' => 2, 'price' => 49.75]
]
];
$response = $this->post('/api/order/create', $orderData, [
'Authorization' => "Bearer {$this->token}"
]);
$this->assertStatus($response, 200);
$this->assertResponseHasKey($response, 'order_id');
}
/**
* 查询订单详情
* @test
*/
public function testGetOrderInfo()
{
$orderId = 1;
$response = $this->get("/api/order/info/{$orderId}", [
'Authorization' => "Bearer {$this->token}"
]);
$this->assertStatus($response, 200);
$this->assertResponseHasKey($response, 'data');
$this->assertArrayHasKey('order_no', $response['body']['data'] ?? []);
}
/**
* 批量断言测试
* @test
*/
public function testApiResponseStructure()
{
$response = $this->get('/api/user/list', [
'Authorization' => "Bearer {$this->token}"
]);
// 多字段断言
$expectedKeys = ['code', 'message', 'data'];
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $response['body'], "Missing key: {$key}");
}
// 类型断言
$this->assertIsInt($response['body']['code'] ?? null);
$this->assertIsString($response['body']['message'] ?? null);
$this->assertIsArray($response['body']['data'] ?? null);
// 范围断言
$this->assertGreaterThan(-1, $response['body']['code'] ?? -1);
$this->assertLessThan(1000, $response['body']['code'] ?? 999);
}
}
使用ThinkPHP内置测试组件
1 创建测试基类(使用ThinkPHP的测试支持)
<?php
// tests/BaseTest.php
namespace tests;
use think\App;
use think\Container;
use PHPUnit\Framework\TestCase;
class BaseTest extends TestCase
{
/** @var App */
protected $app;
/** @var Container */
protected $container;
/** @var \think\Request */
protected $request;
protected function setUp(): void
{
parent::setUp();
$this->app = new App();
$this->container = Container::getInstance();
$this->request = $this->app->request;
}
/**
* 模拟请求
*/
protected function request($method, $url, $data = [], $headers = [])
{
// 构造请求对象
$this->request->setMethod($method);
$this->request->url($url);
// 设置请求数据
$this->request->withInput($data);
// 设置请求头
foreach ($headers as $key => $value) {
$this->request->header([$key => $value]);
}
// 执行路由
$response = $this->app->http->run($this->request);
return $response;
}
/**
* 模拟GET请求
*/
protected function get($url, $params = [], $headers = [])
{
$url = $url . (strpos($url, '?') ? '&' : '?') . http_build_query($params);
return $this->request('GET', $url, [], $headers);
}
/**
* 模拟POST请求
*/
protected function post($url, $data = [], $headers = [])
{
return $this->request('POST', $url, $data, $headers);
}
/**
* 模拟PUT请求
*/
protected function put($url, $data = [], $headers = [])
{
return $this->request('PUT', $url, $data, $headers);
}
/**
* 模拟DELETE请求
*/
protected function delete($url, $headers = [])
{
return $this->request('DELETE', $url, [], $headers);
}
/**
* 从响应中提取数据
*/
protected function responseData($response)
{
$content = $response->getContent();
return json_decode($content, true);
}
}
2 使用ThinkPHP控制器测试
<?php
// tests/Controller/UserControllerTest.php
namespace tests\Controller;
use tests\BaseTest;
use think\facade\Route;
class UserControllerTest extends BaseTest
{
/**
* 测试用户登录控制器
*/
public function testLoginAction()
{
// 定义路由
Route::post('/api/login', 'UserController@login');
$data = [
'username' => 'admin',
'password' => '123456'
];
$response = $this->post('/api/login', $data);
$result = $this->responseData($response);
$this->assertEquals(200, $response->getCode());
$this->assertTrue($result['success']);
$this->assertArrayHasKey('token', $result['data']);
}
/**
* 测试用户列表控制器
*/
public function testListAction()
{
$response = $this->get('/api/users', ['page' => 1]);
$result = $this->responseData($response);
$this->assertEquals(200, $response->getCode());
$this->assertArrayHasKey('users', $result['data']);
$this->assertIsArray($result['data']['users']);
}
}
高级断言技巧
1 数据校验断言
<?php
// tests/Traits/AssertionHelpers.php
namespace tests\Traits;
trait AssertionHelpers
{
/**
* 断言数据为空或null
*/
public function assertEmptyOrNull($value, $message = '')
{
$this->assertTrue(empty($value), $message);
}
/**
* 断言数据格式正确(邮箱、手机号等)
*/
public function assertValidEmail($email, $message = '')
{
$this->assertMatchesRegularExpression(
'/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/',
$email,
$message
);
}
/**
* 断言时间格式
*/
public function assertValidDateTime($datetime, $message = '')
{
$this->assertMatchesRegularExpression(
'/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/',
$datetime,
$message
);
}
/**
* 断言数组包含指定键
*/
public function assertArrayContainsKeys($array, $keys, $message = '')
{
foreach ($keys as $key) {
$this->assertArrayHasKey($key, $array,
$message ?: "Array should contain key '{$key}'");
}
}
/**
* 断言响应成功状态
*/
public function assertResponseSuccess($response, $message = '')
{
$this->assertEquals(200, $response['status'] ?? null, $message);
$this->assertEquals(1, $response['body']['code'] ?? null, $message);
$this->assertEquals('success', $response['body']['message'] ?? null, $message);
}
}
2 断言链式测试
<?php
// tests/ChainedTest.php
namespace tests;
use tests\Traits\AssertionHelpers;
class ChainedTest extends TestCase
{
use AssertionHelpers;
/**
* 链式测试示例
* @test
*/
public function testChainedAssertions()
{
// 模拟响应数据
$response = [
'status' => 200,
'body' => [
'code' => 1,
'message' => 'success',
'data' => [
'user' => [
'id' => 1,
'username' => 'admin',
'email' => 'admin@example.com',
'created_at' => '2023-01-01 10:00:00'
],
'orders' => [
['id' => 1, 'total' => 100],
['id' => 2, 'total' => 200]
]
]
]
];
// 链式断言
$this->assertResponseSuccess($response)
->assertArrayContainsKeys($response['body']['data'], ['user', 'orders'])
->assertValidEmail($response['body']['data']['user']['email'])
->assertValidDateTime($response['body']['data']['user']['created_at'])
->assertCount(2, $response['body']['data']['orders'])
->assertGreaterThan(0, $response['body']['data']['orders'][0]['total']);
}
}
使用测试辅助工具
1 创建测试工具类
<?php
// tests/Helpers/TestHelper.php
namespace tests\Helpers;
class TestHelper
{
/**
* 生成测试数据
*/
public static function generateTestData($type = 'user')
{
switch ($type) {
case 'user':
return [
'username' => 'test_' . time(),
'password' => '123456',
'email' => 'test_' . time() . '@example.com'
];
case 'order':
return [
'order_no' => 'ORD' . time(),
'amount' => rand(100, 9999) / 100
];
default:
return [];
}
}
/**
* 基础认证头
*/
public static function authHeader($token)
{
return [
'Authorization' => "Bearer {$token}",
'Accept' => 'application/json',
'Content-Type' => 'application/json'
];
}
}
2 使用测试工具类
<?php
// tests/Api/IntegrationTest.php
namespace tests\Api;
use tests\TestCase;
use tests\Helpers\TestHelper;
class IntegrationTest extends TestCase
{
/**
* 完整的业务流测试
* @test
*/
public function testCompleteBusinessFlow()
{
// 1. 用户注册
$userData = TestHelper::generateTestData('user');
$registerResponse = $this->post('/api/register', $userData);
$this->assertStatus($registerResponse, 200);
// 2. 用户登录
$loginResponse = $this->post('/api/login', [
'username' => $userData['username'],
'password' => $userData['password']
]);
$this->assertStatus($loginResponse, 200);
$token = $loginResponse['body']['token'];
// 3. 创建订单
$orderData = TestHelper::generateTestData('order');
$orderResponse = $this->post('/api/orders', $orderData,
TestHelper::authHeader($token));
$this->assertStatus($orderResponse, 200);
$orderId = $orderResponse['body']['order_id'];
// 4. 查询订单
$getOrderResponse = $this->get("/api/orders/{$orderId}",
TestHelper::authHeader($token));
$this->assertStatus($getOrderResponse, 200);
// 5. 删除订单
$deleteResponse = $this->delete("/api/orders/{$orderId}",
TestHelper::authHeader($token));
$this->assertStatus($deleteResponse, 200);
}
}
配置文件
<?php
// phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="APP_ENV" value="testing"/>
<env name="DB_HOST" value="127.0.0.1"/>
<env name="DB_NAME" value="test_database"/>
<env name="DB_USER" value="root"/>
<env name="DB_PASS" value=""/>
<env name="DB_PORT" value="3306"/>
</php>
</phpunit>
这个测试框架提供了完整的模拟请求和断言功能,可以根据项目需求灵活使用,建议在开发过程中持续编写和维护测试,确保代码质量和功能稳定性。