13

When writing tests, why would someone want to use an in-memory database over just mocking the data?

I could see that in-memory databases could be beneficial for testing out one's repositories. But if utilizing a framework (such as Spring Data), testing the repositories would be testing the framework and not really the application logic.

Mocking, however, seems quicker and follows the same pattern that's generally employed when writing unit tests and TDD.

So what am I missing? When/why would an in-memory database be beneficial?

15

Mocking is the ideal solution for unit tests, and it may also be used for integration tests to improve speed, but it doesn't provide the same level of confidence as when you use an in-memory database. You should write end-to-end tests where you configure the entire application as close as possible to how it is configured production and run automated tests against it. These tests should use a real database--either in-memory, docker, a VM, or some other deployment.

But if utilizing a framework (such as Spring Data), testing the repositories would be testing the framework and not really the application logic.

By using a real database you're testing that you're actually configuring and using the framework correctly. Further there may be shortcomings in the framework that are only revealed when testing with an actual database (contrived example: Spring Data doesn't support version 9.2 of PostgreSQL).

I would write most of my test coverage against mocked sources, but I would write some end-to-end tests for commonly exercised use cases using a real database.

| improve this answer | |
  • If it's a Unit test, you'd test the framework separately from the layer that uses the framework. There should always be some integration tests after all unit testing is done. – Denise Skidmore May 29 '19 at 18:19
2

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@email.com');

        $this->seeInDatabase('users', ['name' => 'name', 'email' => 'email@email.com']);
    }
}

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@email.com']]);

        $userRepository = new UserRepository($databaseMock);
        $userRepository->create('name', 'email@email.com');
    }
}

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@email.com');

        $this->assertEquals('name', $fakeDatabase->datas['users']['name']);
        $this->assertEquals('email@email.com', $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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy