jrenk
jrenk

Reputation: 1416

Laravel 5.6 Unit Test Call to a member function beginTransaction() on null

I have a Laravel project running version 5.6. The connected database is mongodb with the jenssegers/mongodb package.

I wrote a single Unittest to test a function related to the User.

The test creates new users in a configured test mongodb database. I want to refresh the database after each test run so I use the RefreshDatabase Trait.

When using the RefreshDatabase Trait I get the following error when running my test:

There was 1 error:

1) Tests\Unit\UserTest::it_gets_top_user Error: Call to a member function beginTransaction() on null

When not using the Trait the test creates all the necessary stuff in the database and performs the assertion without an error.

The test looks like this:

/** @test */
public function it_gets_top_user()
{
    factory(\App\Users\User::class, 5)->create();

    $userOne = factory(\App\Users\User::class)->create([
        'growth' => 10
    ]);

    $topUser = Users::getTopUser();

    $collection = new Collection();

    $collection->push($userOne);


    $this->assertEquals($collection, $topUser);
} 

I use the following versions in my composer.json:

"laravel/framework": "5.6.*",
"jenssegers/mongodb": "3.4.*",
"phpunit/phpunit": "~7.0",

The following versions are used on the server:

I call the test with the phpunit installed in the vendor directory with:

vendor/phpunit/phpunit/phpunit

Upvotes: 3

Views: 2907

Answers (1)

jrenk
jrenk

Reputation: 1416

The problem seems to be, that the RefreshDatabase Trait does not work at all for a MongoDB environment.

I solved the above problem by creating my own RefreshDatabase Trait in the testing/ directory of my Laravel project.

The Trait looks like this:

<?php

namespace Tests;

trait RefreshDatabase
{
    /**
     * Define hooks to migrate the database before and after each test.
     *
     * @return void
     */
    public function refreshDatabase()
    {
        $this->dropAllCollections();
    }

    /**
     * Drop all collections of the testing database.
     *
     * @return void
     */
    public function dropAllCollections()
    {
        $database = $this->app->make('db');

        $this->beforeApplicationDestroyed(function () use ($database) {
            // list all collections here
            $database->dropCollection('users');
        });
    }
}

To enable this Trait I have overridden the setUpTraits function in the TestCase Class. It now looks like this:

/**
 * Boot the testing helper traits.
 *
 * @return array
 */
protected function setUpTraits()
{
    $uses = array_flip(class_uses_recursive(static::class));

    if (isset($uses[\Tests\RefreshDatabase::class])) {
        $this->refreshDatabase();
    }

    if (isset($uses[DatabaseMigrations::class])) {
        $this->runDatabaseMigrations();
    }

    if (isset($uses[DatabaseTransactions::class])) {
        $this->beginDatabaseTransaction();
    }

    if (isset($uses[WithoutMiddleware::class])) {
        $this->disableMiddlewareForAllTests();
    }

    if (isset($uses[WithoutEvents::class])) {
        $this->disableEventsForAllTests();
    }

    if (isset($uses[WithFaker::class])) {
        $this->setUpFaker();
    }

    return $uses;
}

And finally in all my testing classes I can use my newly created Trait like this:

<?php

namespace Tests\Unit;

use Illuminate\Database\Eloquent\Collection;
use Tests\RefreshDatabase;
use Tests\TestCase;

class UserTest extends TestCase
{
    use RefreshDatabase;

    // tests happen here
}

Upvotes: 6

Related Questions