You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel 9.x下如何用PHPUnit测试数据库创建方法(TDD方式)

问题分析

你的代码存在几个核心问题,导致测试断言失败且数据残留:

  1. 重写了Laravel Model自带的create方法,且该方法未返回创建的实例,所以assertNotNull断言必然失败
  2. 测试未使用Laravel的数据库测试特性,导致测试数据永久留在数据库中
  3. 用户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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 22:35:41