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

