1

I'm not very much knowledgeable about making PHPUnit and Drupal testing setup.

I am having problems regarding database calls.

I tried to setup phpunit test.

Inside my module/some_module. I installed phpunit by running

composer require --dev phpunit/phpunit ^8

This is my module/some_module/composer.json

{
    "autoload": {
        "classmap": [
            "src/"
        ]
    },
    "require-dev": {
        "phpunit/phpunit": "^8"
    }
}

This is my module/some_module/tests/SampleTest.php

<?php

use PHPUnit\Framework\TestCase;
use Drupal\nat\Controller\SampleController;
use Drupal\Core\Controller\ControllerBase;

final class SampleTest extends TestCase
{
    public function testCanFilterData(): void
    {
        $this->getMockBuilder(ControllerBase::class)
            ->getMock();

        $class = new ReflectionClass(SampleController::class);
        $method = $class->getMethod('filterData');
        $method->setAccessible(true);

        $obj = new SampleController();
        $args = [1, 2, 3];
        $method->invokeArgs($obj, [$args]);

        print_r($foo);
    }
}

This is my module/some_module/src/Controller/SampleController.php

<?php

namespace Drupal\some_module\Controller;

use Drupal\Core\Controller\ControllerBase;

class SampleController extends ControllerBase {
    public function index()
    {
        $ids = [1, 2, 3]
        $data = [];

        $data['some_data'] = $this->filterData($ids);

        return [*
            '#theme' => 'test_template',
            '#data' => $data,
          ];
    }

    private function filterData($ids)
    {
        $connection = \Drupal::database();
        $query = $connection->query("SELECT
                id, name
            FROM products
            WHERE id IN (:ids[])",
            [
                ':ids[]' => $ids,
            ]
        );

        return $query->fetchAll(\PDO::FETCH_ASSOC);
    }
}

When I ran phpunit tests/NatTest.php, I am getting this error:

1) NatTest::testCanBeCreatedFromValidEmailAddress
Error: Class 'Drupal' not found

So my question is how can I test this? I can't mock Drupal because I actually want to execute a database query from Drupal. How can I do this?

2 Answers 2

4

Automated tests describes the different tests and makes clear which test should be used, in a Drupal environment. In particular, it says:

  • Unit tests: Test functionality of a class if the Drupal environment (database, settings, etc.) and web browser are not needed for the test, or if the Drupal environment can be replaced by a "mock" object.
  • Kernel tests: Test functionality of a class if the full Drupal environment and web browser are not needed for the test, but the functionality has significant Drupal dependencies that cannot easily be mocked. Kernel tests can access services, the database, and a minimal mocked file system, and they use an in-memory pseudo-installation. However, modules are only installed to the point of having services and hooks, unless you install them explicitly.

If you are testing a class that should insert data in the database, the test should be a Kernel test, which means:

  • The base class of the test should be \Drupal\KernelTests\KernelTestBase
  • The namespace of the test should start with (in your case) \Drupal\Tests\some_module\Kernel, and it should be placed in the some_module/tests/src/Kernel directory or a sub-directory

For example, the DatabaseBackendTagTest class implements a Kernel test that accesses the database.

  public function testTagInvalidations() {
    // Create cache entry in multiple bins.
    $tags = [
      'test_tag:1',
      'test_tag:2',
      'test_tag:3',
    ];
    $bins = [
      'data',
      'bootstrap',
      'render',
    ];
    foreach ($bins as $bin) {
      $bin = \Drupal::cache($bin);
      $bin->set('test', 'value', Cache::PERMANENT, $tags);
      $this->assertTrue($bin->get('test'), 'Cache item was set in bin.');
    }
    $connection = Database::getConnection();
    $invalidations_before = intval($connection->select('cachetags')->fields('cachetags', ['invalidations'])->condition('tag', 'test_tag:2')->execute()->fetchField());
    Cache::invalidateTags(['test_tag:2']);

    // Test that cache entry has been invalidated in multiple bins.
    foreach ($bins as $bin) {
      $bin = \Drupal::cache($bin);
      $this->assertFalse($bin->get('test'), 'Tag invalidation affected item in bin.');
    }

The code shown in the question just reads data from the database. (I don't see code writing the database.) Keep in mind that the database used from tests isn't automatically populated; if the purpose of the test is verifying the controller outputs the correct data, the database table needs to be filled with the data. That is normally done from the setUp() method, which could also initialize a property with the connection (for example with $this->connection = Database::getConnection();) if the data is the same for each test, or at the beginning of the test method, if each test needs different data. (For example, testCanFilterData() could test the controller filter the data in the database and testCanFilterWrongData() could test the controller doesn't output completely wrong data or out-of-the-set data.)

3
  • I want to connect to the mysql database, how can I populate my DB credentials. Can you provide me a sample code in setUp method how can I initialize the connection property. Commented Jul 18, 2019 at 6:44
  • I have to add some more details to the answer, but I don't have time now.
    – avpaderno
    Commented Jul 18, 2019 at 6:58
  • Okay, can you add the details when you have time. For the meantime I'll try to solve this myself. Commented Jul 18, 2019 at 9:03
0

Here is a documentation about PHPUnit in Drupal 8. https://www.drupal.org/docs/8/phpunit

However what you are trying to create is not a unit test at all, because you are querying an existing database. You need to write an integration test which is a whole different story. For this purpose consider using Behat or Drupal Test Traits.

6
  • 2
    Drupal core has tests using the database; none of them uses Behat, so it's not necessary for this type of tests.
    – avpaderno
    Commented Jul 18, 2019 at 5:41
  • @kiamlaluno Drupal core creates a brand-new Drupal installation for each test. Querying existing site database is not possible.
    – ya.teck
    Commented Jul 18, 2019 at 13:35
  • That's not true. The test code needs to populate the database, in some way. Usually, that is done from the code being tested.
    – avpaderno
    Commented Jul 18, 2019 at 13:58
  • The test can only populate a new database that Drupal created for it. There is no way to test content in existing database.
    – ya.teck
    Commented Jul 18, 2019 at 14:19
  • Tests can populate a database. The fact a database is emptied each time a test runs is done on purpose, so that a test is independent from the previously run tests.
    – avpaderno
    Commented Jul 18, 2019 at 14:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.