Skip to content

Use RefreshDatabase create Call to a member function beginTransaction() on null error #1475

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
theminer3746 opened this issue Mar 27, 2018 · 12 comments

Comments

@theminer3746
Copy link
Contributor

When testing database laravel's documentation suggest using RefreshDatabase trait.
But that cause Error: Call to a member function beginTransaction() on null

@LucasLeandro1204
Copy link

Did you fixed it? @theminer3746

@lscarneiro
Copy link

Would love to know if some of you achieved a solution...

@LucasLeandro1204
Copy link

@lscarneiro I created a trait that resets the db

<?php

namespace Tests;

use DB;
use Jenssegers\Mongodb\Schema\Blueprint;

trait RefreshDatabase
{
    /**
     * Set up function.
     */
    public function setUp(): void
    {
        parent::setUp();

        if ($this->hasDependencies()) {
            return;
        }

        $this->dropAllCollections();
    }

    /**
     * Drop all collections.
     */
    protected function dropAllCollections(): void
    {
        $mongo = DB::connection('mongodb');

        foreach ($mongo->listCollections() as $collection) {
            if (starts_with($name = (string) $collection->getName(), 'system')) {
                continue;
            }

            (new Blueprint($mongo, $name))->drop();
        }
    }
}

@lscarneiro
Copy link

Thanks @LucasLeandro1204.

I wanted a more "generic" approach, so what I did was create a Trait in app\Traits (new folder in my case) with:

<?php

namespace App\Traits;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Foundation\Testing\RefreshDatabaseState;

trait RefreshDatabaseTransactionLess
{
    /**
     * Refresh a conventional test database.
     *
     * @return void
     */
    public function refreshTestDatabase()
    {
        if (!RefreshDatabaseState::$migrated) {
            $this->artisan('migrate:fresh', $this->shouldDropViews() ? [
                '--drop-views' => true,
            ] : []);

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;
        }

    }
}

And in my test class I use both RefreshDatabase and RefreshDatabaseTransactionLess this way:

use RefreshDatabase, RefreshDatabaseTransactionless {
        RefreshDatabaseTransactionless::refreshTestDatabase insteadof RefreshDatabase;
    } 

That tip I got from this StackOverflow post

I consider it a Hack, because it does not solve the original problem, which is execute transactions with this MongoDB Laravel driver.

The code from refreshTestDatabase() inside RefreshDatabaseTransactionLess didn't come out of thin air, it's actually the same code from the original trait, but skipping the call to $this->beginDatabaseTransaction();

@lscarneiro
Copy link

lscarneiro commented Jan 9, 2019

Just thinking a little further.

An even cleaner approach is to instead of overriding refreshTestDatabase(), to actually override beginDatabaseTransaction().

Just tested here and it works like a charm!

in RefreshDatabaseTransactionLess trait

<?php

namespace App\Traits;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Foundation\Testing\RefreshDatabaseState;

trait RefreshDatabaseTransactionLess
{
    /**
     * Begin a database transaction on the testing database.
     *
     * @return void
     */
    public function beginDatabaseTransaction()
    {
        // Nothing! This is on purpose! just skip me!
    }
}

And on the test class:

use RefreshDatabase, RefreshDatabaseTransactionless {
    RefreshDatabaseTransactionless::beginDatabaseTransaction insteadof RefreshDatabase;
}

That way we're achieving the exact "hack" that we want, skip the transaction part (that breaks the test).

I created this Gist containing an example of implementation of the Hack

@lscarneiro
Copy link

Just discovered that in my case, using use DatabaseMigrations; trait, solves my actual problem (reset database after each test) with just one line.

@prashant-pokhriyal
Copy link

@lscarneiro , how to override beginTransaction() method?

@lscarneiro
Copy link

@prashant-pokhriyal sorry for the delay.

If you're talking about the specific $connection->beginTransaction(); on RefreshDatabase trait, I would say that this is not the easiest approach because it is a method from a yet another class.

Which behavior are you targetting specifically?

@prashant-pokhriyal
Copy link

prashant-pokhriyal commented Jan 22, 2019

@lscarneiro Actually I'm using a library which internally uses Transactions. Since laravel-mongodb does not have Transaction, it throws an error: Call to a member function beginTransaction() on null. I somehow want to bypass this.

@lscarneiro
Copy link

I don't think I have a solution for your case, maybe you could try some help with the library issues page, here in my case I didn't change laravel-mongodb code itself, but rather the code that "depends" on transactions per se.

@salalaslam
Copy link

#1334 (comment)

@tnatanael
Copy link

Any news on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants