Thor Correia
Thor Correia

Reputation: 1309

General error: 1 no such table: users in Laravel Unit Test

I have created the following unit test:

    <?php

    namespace Tests\Unit;

    use Tests\TestCase;
    use App\User;
    use App\Organization;

    class UserTest extends TestCase
    {
        public function testUserHasOwnedOrganization()
        {
            $user = factory(User::class)->create();
            $organization = factory(Organization::class)->create([
                'organizer_id' => $user->id,
            ]);

            $this->assertContains($organization, $user->owned_organizations);
        }
    }

When I run it, I get:

SQLSTATE[HY000]: General error: 1 no such table: users

However, when I open up php artisan tinker:

>>> factory(App\User::class)->create()
=> App\User {#3048
     name: "Margret Armstrong",
     email: "[email protected]",
     email_verified_at: "2020-05-18 01:22:30",
     updated_at: "2020-05-18 01:22:30",
     created_at: "2020-05-18 01:22:30",
     id: 1,
   }

So clearly the factory works and the table exists.

What's going on here?

Thanks,

Upvotes: 10

Views: 17169

Answers (3)

soggypants
soggypants

Reputation: 425

For me the problem was in migration file, I tried creating table with

public function up()
{
    Schema::table 

instead of

public function up()
{
    Schema::create

Upvotes: 1

quinny
quinny

Reputation: 710

You are likely testing with the in-memory database (https://en.wikipedia.org/wiki/In-memory_database) without realising it, and it looks like this isn't what you want to do, based upon your question. Easily done, because Laravel's documentation isn't clear that this is Laravel's default phpUnit test config. When your project's testing is configured for in-memory-db testing, the db needs to be built in memory to test against.

Implementing

use RefreshDatabase;

ensures that the db is built in memory if the testing is configured for in-memory-db testing.

How do you switch away from in-memory-db testing to physical db testing? Check your phpunit.xml. You will find environment variables declared as follows:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>

        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="true">
            <directory suffix=".php">./app</directory>
        </whitelist>
    </filter>
    <php>
        <server name="APP_ENV" value="testing"/>
        <server name="BCRYPT_ROUNDS" value="4"/>
        <server name="CACHE_DRIVER" value="array"/>
        <server name="DB_CONNECTION" value="sqllite"/> ##<-- 
## Change this to the default db connection, i.e. "mysql"
        <server name="DB_DATABASE" value=":memory:"/> ## <-- here!!
## Change to something else mainly for your benefit, because the 
## test-environment should pick up your db from your .env.testing
## You have created a .env.testing, right?
        <server name="MAIL_DRIVER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
    </php>
</phpunit>

Further tip:
You don't need to rebuild the db when you're testing against an already-prepared physical db. You'll see if you study the code in vendor/laravel/framework/src/Illuminate/Foundation/Testing/RefreshDatabase.php that RefreshDatabase exists to re-migrate the db from fresh, whether an in-memory-db or a physical db. In your case, you need only to truncate target tables, which you should be able to do in an extended PHPUnit::setUp() protected method. I recommend extended, because there's a significant amount of setup code in Laravel's TestCase::setUp()

class MyTestClass extends TestCase
{

    protected function setUp()
    {
        $this->doMyDbTableClearDown();
        parent::setUp();
    }
}

On further investigation, I found the protected array-variable TestCase::$afterApplicationCreatedCallbacks, which appears it could be used to reference the $this->doMyDbTableClearDown(). I haven't used this so far, but it might prove a more elegant solution to the above.

Upvotes: 2

Giovanni S
Giovanni S

Reputation: 2110

You will need to migrate the DB when you run the test one way or another.

A common way is to utilize the RefreshDatabase trait.

<?php

namespace Tests\Unit;

use Tests\TestCase;
use App\User;
use App\Organization;
use Illuminate\Foundation\Testing\RefreshDatabase;

class UserTest extends TestCase
{
    use RefreshDatabase;

    public function testUserHasOwnedOrganization()
    {
        $user = factory(User::class)->create();
        $organization = factory(Organization::class)->create([
            'organizer_id' => $user->id,
        ]);

        $this->assertContains($organization, $user->owned_organizations);
    }
}

See if that helps you out.

You can find more information about it here: https://laravel.com/docs/7.x/database-testing

Upvotes: 29

Related Questions