Laravel 9.x下如何用PHPUnit测试数据库创建方法(TDD方式)
问题分析
你的代码存在几个核心问题,导致测试断言失败且数据残留:
- 重写了Laravel Model自带的
create方法,且该方法未返回创建的实例,所以assertNotNull断言必然失败 - 测试未使用Laravel的数据库测试特性,导致测试数据永久留在数据库中
- 用户ID硬编码为1,未结合测试环境的用户上下文处理
解决方案
一、修正Project模型的创建逻辑
不要重写Laravel原生的create方法(该方法基于批量赋值实现,重写会破坏框架原有逻辑),改为自定义方法或正确使用原生逻辑:
方式1:自定义创建方法(推荐,语义更清晰)
class Project extends Model { use HasFactory; // 允许批量赋值的字段,按需调整 protected $fillable = ['name', 'user_id']; public static function createForUser(string $name, User $user): self { return self::create([ 'name' => $name, 'user_id' => $user->id, ]); } }
方式2:直接使用原生create方法
如果不需要自定义逻辑,只需配置批量赋值字段即可:
class Project extends Model { use HasFactory; protected $fillable = ['name', 'user_id']; }
二、编写符合TDD的测试(自动清理数据)
Laravel提供两个核心Trait处理测试数据库,确保测试后数据自动清理:
RefreshDatabase:每次测试后重置数据库(适合SQLite内存数据库)DatabaseTransactions:将测试包裹在事务中,结束后自动回滚(适合MySQL等)
选择其中一个Trait引入测试类,同时验证创建逻辑:
use Illuminate\Foundation\Testing\RefreshDatabase; use App\Models\User; use App\Models\Project; class ProjectTest extends TestCase { use RefreshDatabase; // 或DatabaseTransactions public function test_it_creates_a_new_project_for_authenticated_user() { // 1. 创建测试用户 $user = User::factory()->create(); // 2. 调用创建方法 $project = Project::createForUser('Project 1', $user); // 若用原生create:$project = Project::create(['name' => 'Project 1', 'user_id' => $user->id]); // 3. 断言验证 $this->assertNotNull($project); $this->assertEquals('Project 1', $project->name); $this->assertEquals($user->id, $project->user_id); // 可选:断言数据库中存在该记录(测试后会被自动清理) $this->assertDatabaseHas('projects', [ 'name' => 'Project 1', 'user_id' => $user->id, ]); } }
三、模拟用户ID的几种方式
1. 创建测试用户(推荐,贴近真实场景)
通过Laravel的工厂创建真实的测试用户,直接使用其ID:
$user = User::factory()->create(); // 用$user->id作为关联的user_id
2. 模拟认证上下文
如果需要在测试中模拟用户登录状态(比如控制器中依赖Auth::id()),使用actingAs方法:
$user = User::factory()->create(); $this->actingAs($user); // 此时在模型/控制器中调用Auth::id()会返回$user->id
3. 模拟Auth门面(仅适合纯单元测试)
如果不想创建真实用户,可模拟Auth静态方法的返回值:
use Illuminate\Support\Facades\Auth; Auth::shouldReceive('id')->andReturn(123); // 后续调用Auth::id()会返回123
四、关于“不实际写入数据库”的说明
如果要完全避免数据库写入(纯单元测试),可以模拟模型的save或create方法,验证调用逻辑:
use Mockery; use App\Models\Project; public function test_it_prepares_project_data_correctly() { // 模拟Project模型实例 $projectMock = Mockery::mock(Project::class)->makePartial(); $projectMock->shouldReceive('save')->once()->andReturn(true); // 验证数据赋值逻辑 $projectMock->name = 'Project 1'; $projectMock->user_id = 1; $this->assertEquals('Project 1', $projectMock->name); $this->assertEquals(1, $projectMock->user_id); }
注意:集成测试(验证真实数据库交互)更适合TDD的核心验证环节,能确保实际业务逻辑符合预期;纯单元测试仅适合验证代码逻辑的正确性。
内容的提问来源于stack exchange,提问作者Riley Goodman
相关产品推荐
相关产品推荐

