
Laravel 功能测试中,若在模型工厂中手动设置 created_at、updated_at 或 deleted_at 等时间戳字段,会导致内存数据库(SQLite in-memory)中模型虽已创建却无法被 Eloquent 正常查找到,从而触发“Model not found”异常。
laravel 功能测试中,若在模型工厂中手动设置 created_at、updated_at 或 deleted_at 等时间戳字段,会导致内存数据库(sqlite in-memory)中模型虽已创建却无法被 eloquent 正常查找到,从而触发“model not found”异常。
在 Laravel 功能测试中,使用 RefreshDatabase 特性配合内存数据库(如 sqlite::memory:)是高效且隔离性良好的实践。但一个极易被忽视的陷阱是:在自定义模型工厂中显式赋值 Eloquent 自动管理的时间戳字段,会破坏框架内置的时间戳机制,进而导致查询失效。
如问题中所示,TeamFactory 手动设置了:
'deleted_at' => Carbon::now(), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(),
这看似无害,实则引发严重后果:
- ✅ 模型记录确实被插入数据库(
Team::find($id)可返回实例); - ❌ 但 Eloquent 的
findOrFail()或路由模型绑定(如route('team.delete', ['team' => $id]))会因时间戳校验、软删除状态或内部缓存逻辑异常而失败; - ? 尤其在内存数据库中,事务边界与时钟精度更敏感,
Carbon::now()的毫秒级值可能与后续查询时框架生成的默认时间比较逻辑不一致,造成“存在却不可见”的现象。
正确做法:交还时间戳控制权给 Eloquent
应完全移除工厂中对 created_at、updated_at、deleted_at 的手动赋值,让 Laravel 自动处理:
// ✅ 正确的 TeamFactory::definition()
public function definition(): array
{
return [
'id' => Str::uuid(),
'name' => $this->faker->company(), // 使用更贴合的 faker 数据
// 'created_at', 'updated_at', 'deleted_at' —— 全部删除!
];
}
Eloquent 在 save() 或 create() 过程中会自动填充这些字段(包括软删除场景下的 deleted_at 为 null)。若需特定时间(如模拟历史数据),应使用 withTimestamps(false) + 显式设置,并确保逻辑自洽,但常规测试场景绝不推荐。
额外验证建议
为避免类似问题,可在测试中加入断言验证时间戳行为:
public function testTeamFactorySetsTimestampsAutomatically()
{
$team = Team::factory()->create();
$this->assertNotNull($team->created_at);
$this->assertNotNull($team->updated_at);
$this->assertNull($team->deleted_at); // 若启用软删除
$this->assertTrue($team->created_at->lessThanOrEqualTo(now()));
}
总结
- ⚠️ 工厂中手动写入时间戳是 Laravel 测试中典型的“隐性陷阱”,尤其在内存数据库环境下表现诡异;
- ✅ 始终信任 Eloquent 的自动时间戳管理机制;
- ? 遇到“记录存在但查不到”问题时,优先检查工厂定义、软删除状态、数据库连接是否一致(如误用
:memory:多次导致隔离失效); - ? 推荐在
phpunit.xml中配置并指定database_path=:memory:,确保测试环境纯净。
遵循此规范,即可彻底规避“Factory Records Exist but Test Says Otherwise”这类反直觉失败。











