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

Laravel测试断言失败但Seeders运行正常问题排查

问题分析与解决方案

嘿,我帮你找到了问题的根源——这跟函数执行速度完全没关系,问题出在Eloquent的集合缓存机制上!先给你拆解清楚,再给你几个可行的修复方案。

问题回顾

你遇到的情况是:测试套件里的断言总是失败,但同样的逻辑在Seeders里运行正常;第一次执行Seed时会失败,第二次执行时原有数据集没问题,只有新增的数据集会出问题。

相关代码片段

Feature 测试代码

/**
 * Test if User can follow an other user and send a notification
 *
 * @return void
 */
public function test_user_can_follow_an_other_user()
{
    $follower = factory(User::class)->create();
    $followed = factory(User::class)->create();

    $this->actingAs($follower, 'web')
         ->post('/following', ['user' => $followed->id])
         ->assertRedirect('/following');

    $this->assertTrue($follower->follows($followed));
}

PHPUnit 执行结果

PHPUnit 7.1.5 by Sebastian Bergmann and contributors.

.mpoo.F                                                               3 / 3 (100%)

Time: 2.91 seconds, Memory: 50.00MB

There was 1 failure:

1) Tests\Feature\UserTest::test_user_can_follow_an_other_user
Failed asserting that false is true.

/Users/gtr/Code/La-dechetterie-du-web/lddw-api/tests/Feature/UserTest.php:69

FAILURES!
Tests: 3, Assertions: 9, Failures: 1.

Seeder 代码

public function run()
{
    $follower = User::first();
    $following = User::all();
    $followingArrayIds = [];

    foreach ($following as $u) {
        $follower->follow($u);
        array_push($followingArrayIds, [
            'follower' => $follower->id,
            'pretend_to_follow' => $u->id,
            'is '. $follower->id .' following '. $u->id .' ?' => $follower->follows($u) // should be true !!!!!
        ]);
    }

    print_r($followingArrayIds);
}

Model 核心函数

/**
 * User's following relation
 *
 * @return Relation User
 */
public function following(){
    return $this->belongsToMany('App\User', 'followers', 'follower_id', 'following_id');
}

/**
 * Follow an other user
 *
 * @param User $user
 * @return void
 */
public function follow(User $user){
    if($this->follows($user)) return;
    $this->following()->attach($user);
}

/**
 * Check if the current user follows the user he wanted to
 *
 * @param User $user
 * @return boolean
 */
public function follows(User $user){
    return $this->following->contains($user);
}

问题核心:Eloquent的集合缓存陷阱

当你调用$this->following(注意是属性,不是带括号的方法following())时,Eloquent会做两件事:

  1. 立即向数据库发起查询,获取当前用户的所有关注列表
  2. 将查询结果缓存到当前模型实例的following属性里,后续再调用$this->following时,直接返回缓存的集合,不会再查数据库

回到你的测试场景:

  1. 你创建了$follower和$followed两个用户实例
  2. 通过HTTP请求调用follow()方法,成功在数据库中添加了关注关系
  3. 但测试代码里的$follower实例还是最初创建的那个,它的following集合依然是缓存的空集合(因为请求是在独立的容器中运行,不会更新你本地的实例)
  4. 所以follows()方法检查缓存集合,返回false,导致断言失败

至于Seeder里的奇怪表现:

  • 第一次执行时,$follower的following集合初始为空,调用follow()后缓存集合没更新,所以follows()返回false
  • 第二次执行时,原有用户的following集合是从数据库重新加载的(包含之前的关系),所以检查旧用户时返回true;但新增用户的集合还是空的,所以只有新增的会失败——完全符合集合缓存的表现

修复方案(按推荐程度排序)

方案1:重新从数据库获取用户实例(最稳妥)

在断言前,用fresh()方法重新查询用户,这样会加载最新的数据库数据,覆盖缓存的集合:

$this->actingAs($follower, 'web')
     ->post('/following', ['user' => $followed->id])
     ->assertRedirect('/following');

// 重新获取最新的用户数据
$follower = $follower->fresh();

$this->assertTrue($follower->follows($followed));

方案2:修改follows()方法,直接查询数据库

把follows()改成用关系方法查询,这样每次都会直接访问数据库,绕过集合缓存:

public function follows(User $user){
    // 用following()方法(关系查询)替代following属性(缓存集合)
    return $this->following()->where('following_id', $user->id)->exists();
}

这种方式不需要担心缓存问题,唯一的小缺点是每次调用都会发起一次数据库查询,但对于测试和绝大多数业务场景来说,性能完全够用。

方案3:在follow()方法里手动更新缓存集合

如果你想继续使用集合的方式,可以在attach()之后,把用户手动添加到缓存的集合里:

public function follow(User $user){
    if($this->follows($user)) return;
    $this->following()->attach($user);
    // 手动更新缓存的following集合
    $this->following->push($user);
}

注意:如果$this->following还没被访问过(即缓存集合未初始化),这会先触发一次数据库查询,再添加用户,有额外的查询开销,所以不如前两种方案稳妥。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:25:11