Shredding data in in-memory SQLITE database in PHPUnit

I am currently researching how to test a database in PHPUnit framework and ran into a problem that I don't want to connect to a real database in my tests. This is because when I run tests on a different computer, that computer may not have the same database.

I have implemented the trait \PHPUnit\DbUnit\TestCaseTrait

and set the following methods:

/**
 * Returns the test database connection.
 *
 * @return \PHPUnit\DbUnit\Database\Connection
 */
protected function getConnection()
{
    $pdo = new PDO('sqlite::memory:');
    return $this->createDefaultDBConnection($pdo, ':memory:');
}

/**
 * Returns the test dataset.
 *
 * @return \PHPUnit\DbUnit\DataSet\IDataSet
 */
protected function getDataSet()
{
    return $this->createXMLDataSet(dirname(__FILE__) . '/test-dataset.xml');
}

      

The dataset file is present and found correctly.

In my method setUp

from my test, I have set a variable in an object to an instance \PDO

.

/**
 * @var PDO $databaseServerConnection
 */
private $databaseServerConnection;

public function setUp()
{
    $this->databaseServerConnection = $this->getConnection()->getConnection();
}

      

I expected to be able to now use this PDO connection with data retrieved from the dataset file in a method getDataSet()

.

For my own attempt, I tried to compare them with the following code:

# Specify the tables we want to have in our connection dataset
$tables = ['users'];

# Create the dataset in the connection with the tables
$dataset = $this->getConnection()->createDataSet($tables);

# Query all results from the user table  in the connection
$queryTable = $this->getConnection()->createQueryTable(
    'users', 'SELECT * FROM users'
);

# Get the raw table data from the dataset file
$expectedTable = $this->getDataSet()->getTable('users');

# Check if theyre equal
$this->assertTablesEqual($queryTable, $expectedTable);

      

While debugging, I noticed that the array variable $tables

inside is $dataset

just empty. Here a is a var_dump

variable $dataset

.

class PHPUnit\DbUnit\Database\FilteredDataSet#18 (3) {
  protected $tableNames =>
  array(1) {
    [0] =>
    string(5) "users"
  }
  protected $tables =>
  array(0) {
  }
  protected $databaseConnection =>
  class PHPUnit\DbUnit\Database\DefaultConnection#16 (2) {
    protected $connection =>
    class PDO#15 (0) {
    }
    protected $metaData =>
    class PHPUnit\DbUnit\Database\Metadata\Sqlite#17 (6) {
      protected $columns =>
      array(0) {
        ...
      }
      protected $keys =>
      array(0) {
        ...
      }
      protected $truncateCommand =>
      string(11) "DELETE FROM"
      protected $pdo =>
      class PDO#15 (0) {
        ...
      }
      protected $schema =>
      string(8) ":memory:"
      protected $schemaObjectQuoteChar =>
      string(1) """
    }
  }
}

      

Also the array $data

inside the variable $queryTable

is null

. Here a is the var_dump

variable he $queryTable

.

class PHPUnit\DbUnit\DataSet\QueryTable#22 (6) {
  protected $query =>
  string(19) "SELECT * FROM users"
  protected $databaseConnection =>
  class PHPUnit\DbUnit\Database\DefaultConnection#20 (2) {
    protected $connection =>
    class PDO#19 (0) {
    }
    protected $metaData =>
    class PHPUnit\DbUnit\Database\Metadata\Sqlite#21 (6) {
      protected $columns =>
      array(0) {
        ...
      }
      protected $keys =>
      array(0) {
        ...
      }
      protected $truncateCommand =>
      string(11) "DELETE FROM"
      protected $pdo =>
      class PDO#19 (0) {
        ...
      }
      protected $schema =>
      string(8) ":memory:"
      protected $schemaObjectQuoteChar =>
      string(1) """
    }
  }
  protected $tableName =>
  string(5) "users"
  protected $tableMetaData =>
  NULL
  protected $data =>
  NULL
  private $other =>
  NULL
}

      

Whereas the array $data

inside the variable is $expectedTable

filled with data created in the dataset file.

class PHPUnit\DbUnit\DataSet\DefaultTable#30 (3) {
  protected $tableMetaData =>
  class PHPUnit\DbUnit\DataSet\DefaultTableMetadata#34 (3) {
    protected $columns =>
    array(3) {
      [0] =>
      string(2) "id"
      [1] =>
      string(4) "name"
      [2] =>
      string(5) "email"
    }
    protected $primaryKeys =>
    array(0) {
    }
    protected $tableName =>
    string(5) "users"
  }
  protected $data =>
  array(4) {
    [0] =>
    array(3) {
      'id' =>
      string(1) "1"
      'name' =>
      string(3) "test1"
      'email' =>
      string(9) "test1@me.nl"
    }
    [1] =>
    array(3) {
      'id' =>
      string(1) "2"
      'name' =>
      string(3) "test2"
      'email' =>
      string(9) "test2@me.nl"
    }
    [2] =>
    array(3) {
      'id' =>
      string(1) "3"
      'name' =>
      string(6) "test3"
      'email' =>
      string(12) "test3@me.nl"
    }
    [3] =>
    array(3) {
      'id' =>
      string(1) "4"
      'name' =>
      string(4) "test4"
      'email' =>
      string(10) "test4@me.nl"
    }
  }
  private $other =>
  NULL
}

      

I also tried 2 queries on the pdo link object inside the method getConnection()

to create a table with the values ​​inside them:

protected function getConnection()
{
    $pdo = new PDO('sqlite::memory:');
    $pdo->exec("CREATE TABLE users (id PRIMARY KEY, name VARCHAR(50), email VARCHAR(50))");
    $pdo->exec("INSERT INTO users (id, name, email) VALUES (20, 'Bas', 'aa@me')");

    return $this->createDefaultDBConnection($pdo, ':memory:');
}

      

How is it that I have no data available on my connection, and how can I import data from a dataset file here to pass the test?

Also, is it good practice for this?

+3


source to share


1 answer


As far as I can see setUp()

in you TestCase overrides \PHPUnit\DbUnit\TestCaseTrait

setUp()

which contains the logic responsible for setting and disabling .

If you need to have different setUp

in TestCases it might be better to consider making your own base class described in the docs and extend your TestCase and call parent::setUp()

from child TestCase

UPDATE:

You need to create the database, tables, sequences, triggers and views before running the test suite.

taken from here , and there are some helpful hints there.

This basically means that all tables, columns, indexes, constraints and other stuff related to and dependent on the db-related code under test must be set before running the tests. Any content in this db before launching does not matter, all tables that are in the dataset for TestCase will be truncated and filled with data from this dataset.

UPDATE 2: (disclaimer: following my personal preference)

Usually the db is accessed through some kind of gateways. And this is the last thing I implement (at least in the scope of the package). This gives me the opportunity to have real knowledge of the data that needs to be stored in the database when I start making these gateways. So when I start writing some TestCases on some gateways, I just use a management tool (usually some GUI like phpMyAdmin) and create a table (s) and maybe columns that I think should be present in order to store the data with which the gateway is associated. Then, writing the tests down and running them, perhaps in between changing the table structure as it suits better.

With this approach, the entire db structure is created by hand (not by testing the code) and it grows with the code that runs on the db. I find this useful for several reasons.



First, it's easier because I don't need to manage the creation (or re-creation) of the structure before each TestCase. Especially if I have multiple TestCases that work with the same tables.

Second, I always have a db structure that is appropriate for the tests to pass. Any inappropriate changes to the structure will be caught. Also I can always generate sql export statements for this proper structure to initiate a real database with all the required tables, columns, indexes, keys, etc.

Thirdly, sometimes the look and feel should (or even should) be accepted in the database, keeping in mind only db related issues. So I can always open the current db test and see clearly what it is made of.

Note on db in memory. In this case, the structure must be created in code. And here there can be two obvious options - installing a db with a specific structure for the entire test suite or for a specific test case (or a group of them). As for me, I would do the first one for the reasons stated above.

The easiest way to implement is to create a connection and create a structure in the bootrstrap. But I would spend some time and add some dynamics like this:

<?xml version="1.0" encoding="UTF-8" ?>
<phpunit>
    <php>
        <var name="MAKE_IN_MEMORY_DB" value="yes" />
    </php>
</phpunit>

      

and

abstract class AbstractDbTestCase extends \PHPUnit\DbUnit\TestCase
{
    private $connection;

    protected function getConnection() {
        if($this->connection === NULL){
            $this->connection = $this->createConnection();
        }
        return $this->connection;
    }

    private function createConnection(){
        if($GLOBALS['MAKE_IN_MEMORY_DB'] == 'yes'){
            return $this->createInMemory();
        }
        else {
            return $this->createRealDbConnection();
        }
    }

    private function createInMemory(){
        // create connection and set up db;
    }

    private function createRealDbConnection(){
        // create connection using some data from phpunit.xml
    }
}

      

This will make the tests more decoupled from the constraints of the environment β€” all that is required to run the tests is configuration. Actually I would do even more and use a file with sql statements to load inside createInMemory()

(more work needed but I think it's worth it).

+3


source







All Articles