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

Laravel中使用RefreshDatabase测试时无法创建记录且表被截断问题

问题排查与修复方案

我来帮你解决这个测试里的记录保存问题——你遇到的情况是前端提交能正常存储,但用RefreshDatabase的功能测试里POST请求创建的记录找不到,反而测试前的记录被截断,而且换SQLite也有同样问题,无报错但就是没数据。咱们一步步拆解问题:

核心问题分析

首先,RefreshDatabase的作用是每个测试前自动迁移数据库,测试完成后回滚,所以测试前的记录被清空是正常的,但测试中创建的记录应该能在测试内被获取到。现在记录没保存,主要是测试代码的几个细节错误,再加上模型逻辑的小问题:

1. 测试里获取记录的方式错误

你这段代码完全没拿到数据库里的记录:

$patients = new Patient;
$patients->all();
$patient = $patients->last();

new Patient只是创建了一个空的模型实例,$patients->all()不会把结果赋值给变量,所以$patients还是空实例,last()自然拿不到任何数据。

2. 测试请求的参数构造错误

你用factory('App\Patient')->make()生成了一个模型实例,但如果你的Patient Factory是定义了prefix、first_name等单个字段(而不是patient_details字段),那$newPatient->patient_details其实是空的,相当于你POST了空数据到后端——但你说withoutExceptionHandling()没报错,可能你的Factory里刚好定义了patient_details?不过更合理的做法是直接构造符合请求要求的字段数组,而不是依赖模型的patient_details属性。

3. 模型加密逻辑的潜在问题

你的模型creating钩子会加密patient_details,但要确保传入的patient_details是一个数组(而不是字符串),不然加密后解密出来的结果会和预期不符。

修复后的代码

第一步:修正测试代码

<?php
namespace Tests\Feature;

use App\Patient;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Facades\Crypt;
use Tests\TestCase;

class PatientTest extends TestCase
{
    use RefreshDatabase;

    /** @test */
    public function patient_details_are_posted_encrypted_and_saved()
    {
        $this->withoutExceptionHandling();
        
        // 构造符合验证规则的请求参数(直接用faker生成,或者用factory的raw方法)
        $patientData = [
            'first_name' => 'John',
            'last_name' => 'Doe',
            'street_address' => '123 Main St',
            'city' => 'New York',
            'state' => 'NY',
            'zip' => '10001',
            'email' => 'john@example.com',
            // 其他必填/可选字段按需补充
        ];

        // 发送POST请求
        $response = $this->post('/patient', $patientData);

        // 正确获取数据库中的最新记录
        $patient = Patient::latest()->first();

        // 断言解密后的内容和提交的参数一致
        $this->assertEquals($patientData, Crypt::decrypt($patient->patient_details));
    }
}

如果你的Patient Factory已经定义了所有需要的字段,也可以用factory(App\Patient::class)->raw()来生成请求参数,这样更简洁:

$patientData = factory(App\Patient::class)->raw();

第二步:确认模型的加密逻辑

你的模型代码基本没问题,但可以加个小判断,确保只有当patient_details是数组的时候才加密,避免重复加密(比如测试里手动设置的情况):

<?php
namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;

class Patient extends Model
{
    protected $guarded = [];

    public static function boot()
    {
        parent::boot();

        self::creating(function($model){
            // 确保是数组才加密,避免重复加密(比如测试里手动设置的情况)
            if(is_array($model->patient_details)){
                $model->patient_details = Crypt::encrypt($model->patient_details);
            }
        });
    }
}

第三步:验证控制器逻辑

你的控制器代码是对的,但可以确认一下$attributes确实是验证后的数组——因为request()->validate()会返回通过验证的字段,所以没问题。如果还是担心,可以在控制器里加个日志(测试时临时用):

// 在Patient::create之前加一行
\Log::info('Saving patient details:', $attributes);

这样测试时可以查看日志,确认数据是否正确传入。

为什么之前的测试没报错?

因为$this->withoutExceptionHandling()只捕获请求处理中的异常,而你获取记录的方式错误导致$patient是null,但断言的时候Crypt::decrypt(null)会抛出异常吗?不对,你说没报错,可能是$patients->last()返回了null,然后Crypt::decrypt(null)抛出了异常,但你是不是没注意?不过修正获取记录的方式后,这个问题就会解决。

内容的提问来源于stack exchange,提问作者madphp

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:20:18