1

I am trying to do unit testing in laravel 7.x.

I created a testing database which is identic to the database I use for my website.

I modified the phpunit.xml file, I created a .env.testing file and I added a "testing" connection to the database.php file but I am getting an "SQLSTATE[42S02]: Base table or view not found: 1146 Table 'cbs_perform_test.cbs_defis' doesn't exist (SQL: select count(*) as aggregate from cbs_defis)" error.

I already checked multiple times the name of the database and the table to be sure I am using the correct one.

I already followed these guides : Use different database for testing and local ; How to Specify a Separate Database for Unit Testing on Laravel 5

Here is my phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true">
    <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="testing"/>
        <server name="DB_DATABASE" value="cbs_perform_test"/>
        <server name="MAIL_MAILER" value="array"/>
        <server name="QUEUE_CONNECTION" value="sync"/>
        <server name="SESSION_DRIVER" value="array"/>
        <env name="DB_CONNECTION" value="testing"/>
    </php>

My database.php connection

'testing' => [
            'driver' => 'mysql',
            'url' => env('DATABASE_URL'),
            'host' => env('DB_HOST', '127.0.0.1'),
            'port' => env('DB_PORT', '3306'),
            'database' => env('DB_DATABASE', 'forge'),
            'username' => env('DB_USERNAME', 'forge'),
            'password' => env('DB_PASSWORD', ''),
            'unix_socket' => env('DB_SOCKET', ''),
            'charset' => 'utf8mb4',
            'collation' => 'utf8mb4_unicode_ci',
            'prefix' => '',
            'prefix_indexes' => true,
            'strict' => false,
            'engine' => null,
            'options' => extension_loaded('pdo_mysql') ? array_filter([
                PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
            ]) : [],
        ],

my .env.testing file

APP_ENV=testing

DB_CONNECTION=testing
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=cbs_perform_test
DB_USERNAME=root
DB_PASSWORD=

My test code

class DefiTest extends TestCase
{
    use DatabaseMigrations;
    use DatabaseTransactions;

    /**
     * A basic unit test example.
     *
     * @return void
     */
    public function testCreateDefi()
    {
        $request = new Request();
        $request->DEF_NOM = 'test';
        $request->DEF_DESCRIPTION = 'testdescriptio ajhsg ln';
        $request->DEF_NBSEMAINES = 2;
        $request->DEF_CONSEILS = 'jhasnciu launh sl';
        $request->DEF_VISIBLE = 1;
        $request->DEF_DATE_VISIBLE = Carbon::now()->toDate();
        $request->COA_ID = 3;

        $dfc = new DefiCoachController();
        $response = $dfc->createDefiTest($request);

        $this->assertDatabaseHas('cbs_defis', $request->all());
    }
}

The command I use for testing : php artisan test --env=testing

bliblop
  • 13
  • 4

1 Answers1

4

First of all, hope I can help you fix your problem as I am pretty sure it is a silly mistake you are making somewhere in the connection.

So, here are some tips:

Do not test your code "invoking" core framework code...

Instead of doing (unit testing):

$request = new Request();
$request->DEF_NOM = 'test';
$request->DEF_DESCRIPTION = 'testdescriptio ajhsg ln';
$request->DEF_NBSEMAINES = 2;
$request->DEF_CONSEILS = 'jhasnciu launh sl';
$request->DEF_VISIBLE = 1;
$request->DEF_DATE_VISIBLE = Carbon::now()->toDate();
$request->COA_ID = 3;

$dfc = new DefiCoachController();
$response = $dfc->createDefiTest($request);

$this->assertDatabaseHas('cbs_defis', $request->all());

Do (feature test):

$data = [
    'nom' => 'test',
    'description' => 'testdescriptio ajhsg ln',
    'nbsemaines' => 2,
    'conseils' => 'jhasnciu launh sl',
    'visible' => 1,
    'date_visible' => Carbon::now()->toDate(),
    'coa_id' => 3,
];

$response = $this->post('your_desired_url_for_this_action', $data); // This can be get, post, put or delete

$this->assertDatabaseHas('cbs_defis', $data);

This way, you can make sure that:

  1. Your URL is the one you want, without any typo or any errors
  2. The controller is doing what it is supposed to do, inserting data in this case.
  3. The controller is inserting the data you want it to insert. Let's say you have some processing behind curtains, here you can make sure that you sent "1 and 3" and it inserted "role X" (it is an example, let's say that would be your desired result after processing 1 and 3, so you are not directly inserting 1 and 3)
  4. always avoid asserting data from where you are testing it. In your case, you are using Request object, let's say it is your custom class, and you do something when you do $request->attribute1 = 2, so when you read it back as $request->attribute1 maybe you have done some process to store it and you have modified it... if you are asserting that without explicitly saying assert that attribute1 is what I expect you are never asserting it. If you have a mistake in your code and instead of returning b (1 = a, 2 = b, etc.) the code will always pass, because you have stored it as something else than expected, but you are asserting for what it has done (let's say that your mistake returned c instead of b) so you are saying "find $request->attribute1 in the database" and you will have stored c instead of b (your expected value) and it will still find it and pass the test.

There is no need to create a new connection if it is the same except for DB_DATABASE or similar. In that case, you just define that info in .env.testing or in your phpunit.xml.

Also, no need to do <server name="DB_CONNECTION" value="testing"/> and <env name="DB_CONNECTION" value="testing"/>. If you see Laravel GitHub's phpunit.xml, you will see that they changed <env> to <server> on 5.7+, so stick to the one that corresponds to your version. There is a difference though that I cannot remember now, but for testing, there is no problem.


So, make sure you have set the right DB_HOST, DB_PORT, DB_USERNAME and DB_PASSWORD. You could have the same host but different port, or you could have same host and port but different database name, but same username and password. So make sure you are connecting to the correct database.

As your error is that it cannot find the desired table, clearly you are connecting to a database, so username and password should not be your problem, but the table does not exist.


One last important thing, are you using any trait on your tests ? There are some traits to automatically migrate the database and roll it back when finished, so there is no need for you to have your migrations sync manually in the testing environment. You should be using use RefreshDatabase; trait to do so.


Last tip, try to avoid doing DEF_SOMETHING because:

  1. If your controller is related to Defi, there is no need to say "this is DEF data", we already know, so you can directly do something. Same for database, if table name is cars, avoid doing car_wheels, car_doors, etc., do wheels, doors, etc.
  2. Avoid doing X_Y, prefer to do x_y, same for database. Stick to lowercase always and, for database, stick to snake_case, but for models' attributes, always stick to camelCase. (more info about cases)
matiaslauriti
  • 7,065
  • 4
  • 31
  • 43