Most of the time, in-memory database testing is simpler than mocking. It's also a lot more flexible. And it also tests the migration files are done well (when there are migration files).
See this pseudo code :
class InMemoryTest
{
/** @test */
public function user_repository_can_create_a_user()
{
$this->flushDatabase();
$userRepository = new UserRepository(new Database());
$userRepository->create('name', '[email protected]');
$this->seeInDatabase('users', ['name' => 'name', 'email' => '[email protected]']);
}
}
class MockingDBTest
{
/** @test */
public function user_repository_can_create_a_user()
{
$databaseMock = MockLib::mock(Database::class);
$databaseMock->shouldReceive('save')
->once()
->withArgs(['users', ['name' => 'name', 'email' => '[email protected]']]);
$userRepository = new UserRepository($databaseMock);
$userRepository->create('name', '[email protected]');
}
}
The InMemoryTest
doesn't depend on how Database
is implemented into UserRepository
to work. It simply uses the UserRepository
public interface (create
) and then asserts against it. That test won't break if you change the implementation but it's slower.
Meanwhile, the MockingDBTest
relies fully on how Database
is implemented into UserRepository
. In fact, if you change the implementation but still make it work another way, that test would break.
Best of both worlds would be using a fake implementing the Database
interface :
class UsingAFakeDatabaseTest
{
/** @test */
public function user_repository_can_create_a_user()
{
$fakeDatabase = new FakeDatabase();
$userRepository = new UserRepository($fakeDatabase);
$userRepository->create('name', '[email protected]');
$this->assertEquals('name', $fakeDatabase->datas['users']['name']);
$this->assertEquals('[email protected]', $fakeDatabase->datas['users']['email']);
}
}
interface DatabaseInterface
{
public function save(string $table, array $datas);
}
class FakeDatabase implements DatabaseInterface
{
public $datas;
public function save(string $table, array $datas)
{
$this->datas[$table][] = $datas;
}
}
That's way more expressive, easier to read and to understand, and it doesn't depend on the implementation of the actual Database done in higher layers of the code.