|
1 | | -# DatabaseTestCase |
| 1 | +# cspray/database-testing |
2 | 2 |
|
3 | | -A library to facilitate testing database interactions using PHPUnit 10+. |
| 3 | +A low-level, framework-agnostic library for setting up a database suitable |
| 4 | +for automated tests. This library provides the following features: |
4 | 5 |
|
5 | | -Features this library currently provides: |
| 6 | +- The `Cspray\DatabaseTesting\ConnectionAdapter` interface that defines how this library, |
| 7 | + and those that extend it, interact with your database. |
| 8 | +- Comprehensive, customizable strategies for cleaning up your database to ensure each |
| 9 | + test works with a known state. |
| 10 | +- Declare fixtures, sets of database records, that are loaded for each test. |
| 11 | +- A simple interface to easily introspect the contents of a database |
| 12 | + table in your test suite. |
| 13 | +- Database and testing-framework agnostic approach |
6 | 14 |
|
7 | | -- Handles typical database setup and teardown |
8 | | -- Simple representation of a table's rows |
9 | | -- Mechanism for loading fixture data specific to each test |
| 15 | +It cannot be emphasized enough; this library does not provide a turn-key, usable solution |
| 16 | +out-of-the-box. If you use this library directly, instead of one of the extensions that |
| 17 | +targets a specific database connection and testing framework, you'll need to ensure the |
| 18 | +appropriate concrete implementations and framework integration points are created. |
10 | 19 |
|
11 | | -Features this library **does not** currently provide, but plans to: |
| 20 | +## Complete Libraries |
12 | 21 |
|
13 | | -- Semantic assertions on the state of a database |
14 | | -- Representation for the information schema of a given table |
| 22 | +Instead of installing this library directly, we recommend that you install one of the |
| 23 | +available options from the "Connection Adapter" and "Testing Extension" list. Chances are, |
| 24 | +you'll need both. If you don't see your database connection type or testing framework |
| 25 | +listed, please submit an issue to this repository! |
15 | 26 |
|
16 | | -The rest of this document details how to install this library, make use of its `TestCase`, and what database |
17 | | -connection objects are supported out-of-the-box. |
| 27 | +### Connection Adapter |
18 | 28 |
|
19 | | -## Installation |
| 29 | +- [cspray/database-testing-pdo](https://github.com/cspray/database-testing-pdo) |
20 | 30 |
|
21 | | -[Composer](https://getcomposer.org/) is the only supported method for installing this library. |
| 31 | +### Testing Extension |
22 | 32 |
|
23 | | -``` |
24 | | -composer require --dev cspray/database-test-case |
25 | | -``` |
| 33 | +- [cspray/database-testing-phpunit](https://github.com/cspray/database-testing-phpunit) |
26 | 34 |
|
27 | | -## Usage Guide |
28 | | - |
29 | | -Using this library starts by creating a PHPUnit test that extends `Cspray\DatabaseTestCase\DatabaseTestCase`. This class |
30 | | -overrides various setup and teardown functions provided by PHPUnit to ensure that a database connection is established |
31 | | -and that database interactions happen against a known state. The `DatabaseTestCase` requires implementations |
32 | | -to provide a `Cspray\DatabaseTestCase\ConnectionAdapter`. This implementation is ultimately responsible for calls to the |
33 | | -database required by the testing framework. The `ConnectionAdapter` also provides access to the underlying connection, |
34 | | -for example a `PDO` instance, that you can use in your code under test. Check out the section titled "Database Connections" |
35 | | -for `ConnectionAdapter` instances supported out-of-the-box and how you could implement your own. |
36 | | - |
37 | | -In our example, going to assume that you have a PostgreSQL database with a table that has |
38 | | -the following DDL: |
39 | | - |
40 | | -```postgresql |
41 | | -CREATE TABLE my_table ( |
42 | | - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), |
43 | | - username VARCHAR(255), |
44 | | - email VARCHAR(255), |
45 | | - is_active BOOLEAN |
46 | | -) |
47 | | -``` |
| 35 | +## Quick Example |
48 | 36 |
|
49 | | -Now, we can write a series of tests that interact with the database. |
| 37 | +This example is intended to reflect what should be capable with this library. We're going to |
| 38 | +use PHPUnit as our testing framework, it is ubiquitous and likely the framework you'll start off |
| 39 | +using with this library. |
50 | 40 |
|
51 | 41 | ```php |
52 | 42 | <?php declare(strict_types=1); |
53 | 43 |
|
54 | | -namespace Cspray\DatabaseTestCase\Demo; |
55 | | - |
56 | | -use Cspray\DatabaseTestCase\DatabaseRepresentation\Row;use Cspray\DatabaseTestCase\DatabaseTestCase; |
57 | | -use Cspray\DatabaseTestCase\LoadFixture;use Cspray\DatabaseTestCase\SingleRecordFixture;use PDO; |
58 | | - |
59 | | -class MyDemoTest extends DatabaseTestCase { |
60 | | - |
61 | | - // Generally speaking you shouldn't call this method yourself! |
62 | | - protected static function getConnectionAdapter() : ConnectionAdapter { |
63 | | - // Be sure to change these configuration values to match your test setup! |
64 | | - return new PdoConnectionAdapter( |
65 | | - new ConnectionAdapterConfig( |
66 | | - database: 'postgres', |
67 | | - host: 'localhost', |
68 | | - port: 5432, |
69 | | - user: 'postgres', |
70 | | - password: 'postgres' |
71 | | - ), |
72 | | - PdoDriver::Postgresql |
73 | | - ); |
74 | | - } |
75 | | - |
76 | | - public function testUnderlyingConnection() : void { |
77 | | - // You'd pass the value of this method into your code under test |
78 | | - // Use a different ConnectionAdapter if you aren't working with PDO! |
79 | | - self::assertInstanceOf(PDO::class, self::getUnderlyingConnection()); |
80 | | - } |
| 44 | +namespace Cspray\DatabaseTesting\Demo; |
| 45 | + |
| 46 | +use Cspray\DatabaseTesting\DatabaseCleanup\TransactionWithRollback; |
| 47 | +use Cspray\DatabaseTesting\Fixture\LoadFixture; |
| 48 | +use Cspray\DatabaseTesting\Fixture\SingleRecordFixture; |
| 49 | +use Cspray\DatabaseTesting\RequiresTestDatabase; |
| 50 | +use Cspray\DatabaseTesting\TestDatabase; |
| 51 | +use PHPUnit\Framework\TestCase; |
| 52 | +use PDO; |
| 53 | + |
| 54 | +#[RequiresTestDatabase( |
| 55 | + // this should be implemented by you or provided by an extension to this library |
| 56 | + new MyPdoConnectionAdapterFactory(), |
81 | 57 |
|
82 | | - public function testShowEmptyTable() : void { |
83 | | - // DatabaseTestCase provides a method to get a representation of a database table |
84 | | - $table = $this->getTable('my_table'); |
85 | | - |
86 | | - // The $table is Countable, the count represents the number of rows in the table |
87 | | - self::assertCount(0, $table); |
88 | | - |
89 | | - // The $table is iterable, each iteration yields a Row, but our database is empty! |
90 | | - self::assertSame([], iterator_to_array($table)); |
| 58 | + // you could also use Cspray\DatabaseTesting\DatabaseCleanup\TruncateTables |
| 59 | + // or implement your own Cspray\DatabaseTesting\DatabaseCleanup\CleanupStrategy |
| 60 | + new TransactionWithRollback() |
| 61 | +)] |
| 62 | +final class RepositoryTest extends TestCase { |
| 63 | + |
| 64 | + private PDO $pdo; |
| 65 | + private MyRepository $myRepository; |
| 66 | + |
| 67 | + protected function setUp() : void { |
| 68 | + // be sure to use the connection from TestDatabase! depending on CleanupStrategy, |
| 69 | + // using a different connection could wind up with a dirty database state |
| 70 | + $this->pdo = TestDatabase::connection(); |
| 71 | + $this->myRepository = new MyRepository($this->pdo); |
91 | 72 | } |
92 | 73 |
|
93 | | - // Pass any number of Fixture to have corresponding FixtureRecords inserted into |
94 | | - // the database before your test starts |
| 74 | + // populate with more appropriate data. recommended to implement your own |
| 75 | + // Cspray\DatabaseTesting\Fixture\Fixture to reuse datasets across tests |
95 | 76 | #[LoadFixture( |
96 | | - new SingleRecordFixture('my_table', ['username' => 'cspray', 'email' => ' [email protected]', 'is_active' => true]), |
97 | | - new SingleRecordFixture('my_table', ['username' => 'dyana', 'email' => ' [email protected]', 'is_active' => true]) |
| 77 | + new SingleRecordFixture('my_table', [ |
| 78 | + 'name' => 'cspray', |
| 79 | + 'website' => 'https://cspray.io' |
| 80 | + ]) |
98 | 81 | )] |
99 | | - public function testLoadingFixtures() : void { |
100 | | - $table = $this->getTable('my_table'); |
| 82 | + public function testTableHasCorrectlyLoadedFixtures() : void { |
| 83 | + $table = TestDatabase::table('my_table'); |
101 | 84 |
|
102 | | - self::assertCount(2, $table); |
103 | | - self::assertContainsOnlyInstancesOf(Row::class, iterator_to_array($table)); |
104 | | - self::assertSame('cspray', $table->getRow(0)->get('username')); |
105 | | - self::assertSame(' [email protected]', $table->getRow(1)->get('email')); |
106 | | - self::assertNull($table->getRow(2)); |
| 85 | + self::assertCount(1, $table); |
| 86 | + |
| 87 | + self::assertSame('cspray', $table->row(0)->get('name')) |
| 88 | + self::assertSame('website', $table->row(0)->get('website')); |
107 | 89 | } |
108 | | - |
| 90 | + |
109 | 91 | } |
110 | 92 | ``` |
111 | 93 |
|
112 | | -### TestCase Hooks |
113 | | - |
114 | | -There are several critical things the `DatabaseTestCase` must take care of for database tests to work properly. To do that |
115 | | -we must do something in all the normally used PHPUnit `TestCase` hooks. To be clear those methods are: |
116 | | - |
117 | | -- `TestCase::setUpBeforeClass` |
118 | | -- `TestCase::setUp` |
119 | | -- `TestCase::tearDown` |
120 | | -- `TestCase::tearDownAfterClass` |
121 | | - |
122 | | -To make sure that `DatabaseTestCase` processes these hooks correctly they have been marked as `final`. There are new |
123 | | -methods that have been provided that allow for the same effective hooks. |
124 | | - |
125 | | -| Old Hook | New Hook | |
126 | | -| --- |--------------------------------| |
127 | | -| `TestCase::setUpBeforeClass` | `DatabaseTestCase::beforeAll` | |
128 | | -| `TestCase::setUp` | `DatabaseTestCase::beforeEach` | |
129 | | -| `TestCase::tearDown` | `DatabaseTestCase::afterEach` | |
130 | | -| `TestCase::tearDownAfterClass` | `DatabaseTestCase::afterAll` | |
131 | | - |
132 | | -## Database Connections |
133 | | - |
134 | | -| Connection Adapter | Connection Instance | Library | Database | Implemented | |
135 | | -|--------------------------------------------------------|-----------------------------|-----------------------------------|-----------|------------| |
136 | | -| `Cspray\DatabaseTestCase\PdoConnectionAdapter` | `PDO` | [PHP PDO][pdo] | PostgreSQL | :white_check_mark: | |
137 | | -| `Cspray\DatabaseTestCase\PdoConnectionAdapter` | `PDO` | [PHP PDO][pdo] | MySQL | :white_check_mark: | |
138 | | -| `Cspray\DatabaseTestCase\AmpPostgresConnectionAdapter` | `Amp\Postgres\PostgresLink` | [amphp/postgres@^2][amp-postgres] | PostgreSQL | :white_check_mark: | |
139 | | -| | `Amp\Mysql\MysqlLink` | [amphp/mysql@^3][amp-mysql] | MySQL | :x: | |
140 | 94 |
|
141 | | -[amp-mysql]: https://github.com/amphp/mysql |
142 | | -[amp-postgres]: https://github.com/amphp/postgres |
143 | | -[pdo]: https://php.net/pdo |
0 commit comments