
Laravel 功能测试中,若在模型工厂中手动设置 created_at、updated_at 或 deleted_at 等时间戳字段,会导致内存数据库(SQLite in-memory)中记录虽存在但 Eloquent 查询失败,典型表现为“Model not found”异常,而原始 SQL 查询却能查到数据。
laravel 功能测试中,若在模型工厂中手动设置 `created_at`、`updated_at` 或 `deleted_at` 等时间戳字段,会导致内存数据库(sqlite in-memory)中记录虽存在但 eloquent 查询失败,典型表现为“model not found”异常,而原始 sql 查询却能查到数据。
在 Laravel 的功能测试中,尤其是使用 RefreshDatabase 特性配合内存数据库(如 sqlite::memory:)时,Eloquent 的时间戳自动管理机制与手动干预之间存在关键冲突——这正是你遇到 "No query results for model" 错误的根本原因。
? 问题根源分析
你的 TeamFactory 中显式设置了时间戳:
'deleted_at' => Carbon::now(), 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(),
这看似无害,实则破坏了 Laravel 的时间戳生命周期契约:
- Eloquent 默认在
save()时由模型自身自动填充created_at/updated_at(受$timestamps = true和protected $dates控制); - 若工厂中提前写入固定时间值(尤其是
Carbon::now()),该时间可能早于事务开启时刻,或与内存数据库的时钟上下文不一致; - 更严重的是:当模型启用软删除(
SoftDeletes)且deleted_at被设为非-null 值(如Carbon::now())时,Eloquent 默认会隐藏所有deleted_at IS NOT NULL的记录——即使你尚未调用delete(),该记录在常规查询中已被逻辑过滤掉,Team::find($id)失败,而底层DB::table('teams')->where('id', ...)->first()却能查到(因绕过 Eloquent 作用域)。
✅ 验证方式:
// 在测试中添加调试 dump(Team::withTrashed()->find($this->team->id)); // 可能返回结果 dump(Team::find($this->team->id)); // 返回 null(因 deleted_at 非空)
✅ 正确的工厂写法(推荐)
移除所有手动时间戳赋值,交还控制权给 Eloquent:
// database/factories/TeamFactory.php
class TeamFactory extends Factory
{
protected $model = Team::class;
public function definition(): array
{
return [
'id' => Str::uuid(),
'name' => $this->faker->company(), // 使用更语义化的 faker 方法
// ❌ 删除以下三行:
// 'deleted_at' => Carbon::now(),
// 'created_at' => Carbon::now(),
// 'updated_at' => Carbon::now(),
];
}
}
? 提示:Laravel 会自动处理
created_at/updated_at;若需软删除测试,应使用softDeleted()状态方法,而非硬编码deleted_at。
? 补充建议:确保测试环境一致性
-
确认模型启用了时间戳(默认开启,但需检查):
// App\Models\Team.php public $timestamps = true; // 必须为 true use SoftDeletes; // 若启用软删除,确保此 trait 存在
-
避免在工厂中生成重复 UUID 冲突(可选增强):
'id' => fn () => (string) Str::uuid(),
-
在测试中显式验证记录状态:
public function testUserCanDeleteItsOwnTeam() { $this->createTeam(); // 确保记录处于活跃状态(未被软删除) $this->assertNotNull($this->team->refresh()->created_at); $this->assertNull($this->team->deleted_at); $this->useToken() ->deleteJson(route('team.delete', ['team' => $this->team->id])) ->assertOk(); }
? 总结
- ⚠️ 永远不要在工厂中手动设置
created_at/updated_at/deleted_at—— 这违背 Laravel 的自动化约定,尤其在内存数据库中引发不可预测的行为; - ✅ 工厂职责是定义业务属性(如
name,slug, 关联关系等),时间戳和软删除状态应由 Eloquent 模型层统一管控; - ? 遇到 “记录存在但查不到” 类问题,优先检查:软删除状态、时间戳干扰、数据库连接隔离性(是否真在同一个事务/内存实例中)。
修复后,你的 testUserCanDeleteItsOwnTeam 将稳定通过,且符合 Laravel 最佳实践。











