Approaches for PHPUnit tests using a database

To be executed often unit tests need to run fast and there should be no problems with their initial configuration. Otherwise, nobody would use them as a tool to improve the whole process, but threat them like annoying requirements.

Most problems cause tests using a database. How to setup? How to make it run fast and without problem on CI machine as on developers computers?

Approaches for unit tests using a database

There are several ways to maintain a database schema for test purposes. The most popular are:


Lost in computer


What about the initial data in the database

Again there are three ways(have I mentioned 3 is the magic number?) to use initial data in the schema:

/**
 * @test
 */
public function shouldGetCompanyUsers()
{
    //given
    $company = \Tests\CompanyFactory::createCompany();  //create company and insert to db
    $newUser = \Tests\UserFactory::createUserForCompany($company->id);

    //when
    $users = User::getAllForCompany($company->id);
    
    //then
    $this->assertEquals($newUser->id, $users[0]->id);
}

When reading it the developer can see exactly what data was created and it’s easy to understand how the tested function should work.

/**
 * @test
 */
public function shouldGetCategories()
{
    //given
    $this->addFixtures([
        new Companies(),
        new Users()
    ]);
    $company = Company::where()->first();  //additional fetch for fixture data

    //when
    $fetchUsers = User::getAllForCompany($company->id);
    
    //then
    $this->assertCount(1, $fetchUsers);
    
    $newUser = User::where()->first();  //additional fetch for fixture data
    $this->assertEquals($newUser->id, $fetchUsers[0]->id); //to make this assertion works 
}
/**
 * @test
 */
public function shouldGetCategories()
{
    //given
    $companyId = 1; //where that data comes from? Ah... specific `id` from initial data - bad approach!
    //or:
    $company = Company::where(['name'=>'companyWithUser'])->first();  //need to distinguish somehow companies

    //when
    $fetchUsers = User::getAllForCompany($company->id);
    
    //then
    $this->assertCount(1, $fetchUsers);
    
    $newUser = User::where(['name'=>'userWithCompany'])->first();  //additional fetch for schema data
    $this->assertEquals($newUser->id, $fetchUsers[0]->id); //to make this assertion works 
}

Many developers use an id to bind schema data to the tests. At the beginning it looks harmless, but later it makes complete madness when reading the tests. Every time you have to go to the file initial_data_new_new.sql and look for id=2321365, to know what is happening in test,it really doesn’t make your life easier.

Lost in computer