laravel/framework

hasOneThrough failing during numerous model testing on MySQL

Closed this issue ยท 17 comments

  • Laravel Version: 5.8.19
  • PHP Version: 7.3.2
  • Database Driver & Version: MySQL 8.0.15

Description:

I'm having a very strange issue wherein testing a hasOneThrough relationship is passing when I run the test individually, but when I run it as a suite of other tests it fails. To be clear, the suite is a bunch of unit tests that for model classes. Each test class uses the RefreshDatabase trait, so no two tests should affect one another.

Steps To Reproduce:

Here is my test:

    public function testShouldRetrieveCampaign()
    {
        $user     = factory(User::class)->create();
        $team     = factory(Team::class)->create(['owner_id' => $user->id]);
        $campaign = factory(Campaign::class)->create(['team_id' => $team->id]);
        $donation = factory(Donation::class)->create(['user_id' => $user->id, 'campaign_id' => $campaign->id]);
        $comment  = factory(DonationComment::class)->create(['donation_id' => $donation->id]);

        $this->assertTrue($comment->donation->campaign->is($campaign)); // works
        $this->assertTrue($comment->campaign->is($campaign)); // fails
    }

Couple very important things to note here:

  • When the test is run individually it passes; it only fails when I run all the model tests.
  • See the "works" and "fails" lines in the test. The "works" line works every time and is simply retrieving the middle model explicitly. The "fails" line is using a hasOneThrough eloquent relationship is the proper test. This is critical as it means that it's the hasOneThrough that's the issue.

I just updated to Laravel 5.8.32 to see if this has been fixed between versions, and unfortunately the problem persists.

Please create a fresh Laravel installation, add the code necessary to reproduce the issue and upload it to GitHub.

@staudenmeir Will do. Few more notes on my current repo.

I stopped any previous tests from running. So long as this class' tests were the first, they worked fine. I added one test in a previous class with a simple $this->assertTrue(true) and it worked fine. But then I added the following:

public function testBreakage()
{
  factory(User::class)->create()
}

This caused the test to fail wherein the DonationComment accesses the User as a hasOneThrough. So it seems as though something about has a pre-existing model of the same kind that's trying to be retrieved via hasOneThrough causes it to fail.

But here's another interesting detail: This only seems to be an issue between test classes, as I'm creating users in the same class as the failing one, but when I run all tests within the test class then everything passes.

So the problem can currently be described as: When another test class creates a model that is the same as the model being retrieved via hasOneThrough, it causes that relationship to break.


Lastly, I'll note that I haven't seen this issue occur in production code, only in the tests.

Hey there,

Can you first please try one of the support channels below? If you can actually identify this as a bug, feel free to report back and I'll gladly help you out and re-open this issue.

Thanks!

@driesvints Please reopen this issue. I've reproduced the bug in a fresh Laravel installation with minimal setup: https://github.com/JasonTheAdams/laravel-hasonethrough-bug

@JasonTheAdams can you please try a support channel from above first?

@driesvints Could you please provide some direction as to what I should get support for? I've reproduced the bug in a minimal state. Should I get some others to test it out and confirm it? I'm just not sure what you're asking me to do.

@JasonTheAdams we haven't had reports of this up until now which is why I suspect there's something amis in your app that's causing this. Maybe letting others also reproduce it first can definitely help in confirming the bug.

@driesvints I appreciate your suspicion and it makes sense. I reached out on Discord and I'll try to get some other folks to look at it. If you have 10 minutes, the repo I made is very bare bones, so if I am doing something odd it should be quickly obvious.

@JasonTheAdams just a small fyi: if you ever run in a situation again where you need to create a repo to show a bug try to commit the skeleton first and then all your changes which reproduce the bug. That really helps to find out what's amis.

@jason not 100% sure but your Donation doesn't has a relationship to the user so not sure how that would work in the first place?

@driesvints Yeah, good call on committing the skeleton first. I'll keep that in mind in the future.

The donation table has a relationship, and the hasOneThrough doesn't require a middle model relationship as it just creates a double-join query under the hood. The donation table does have a user_id column, which is used for the join.

I did just learn one fun fact from someone on Discord โ€” which I verified โ€” the issue goes away when I run the tests using SQLite. Otherwise I've been using MySQL 8.0.15, which leads me to believe that's where the issue is.

I'm really not even sure where to go by this point. I just tested MySQL 5.7.26 and the issue happened there as well. It doesn't seem to occur with hasManyThrough, either. ๐Ÿ˜•

@driesvints Have any recommendations on how to proceed? I've reproduced the problem on vanilla Laravel and have narrowed it down to MySQL in some form (since SQLite works).

The relationship is incorrect:

class Comment extends Model
{
    public function user()
    {
        return $this->hasOneThrough(
            User::class, Donation::class, 'id', 'id', 'donation_id', 'user_id'
        );
    }
}

Take a look at the executed query with the original relationship: The test only "worked" because the user and the donation have the same id.

@staudenmeir Are you kidding!?

What!?

Oh man... thank you. I can't believe all of this was the result of me just not using the method properly and getting stuck behind a terrible coincidence.

I sincerely thank you for taking the time to point me in the right direction. I shall now eat my big piece of humble pie and move onto the next task.

@JasonTheAdams this is the reason why you should try a support channel first. Don't get me wrong, we're happy to help but a lot of these things can be solved by help from the community. If we had to provide support for every single issue a community member has than we would be doing support for that 24/7 ๐Ÿ˜…

Anyway, glad you found it ๐Ÿ‘