CakePHP Fixture Factories

Juan Pablo Ramirez - Nicolas Masson

Test Driven Developers

Juan Pablo Ramirez
PHP developer at Passbolt
Nicolas Masson
PHP developer at [B]-projects

Based in Luxembourg

Open source password manager

github.com/passbolt

Installable on any Linux server + hosted on cloud

Based on OpenGPG

Collaborative

RESTFull API

Built in CakePHP

TDD oriented

Test database

3 steps

Schema creation

Cleanup

Test Fixtures

  • Testing overview
  • CakePHP's native ORM
  • CakePHP Fixture Factories
  • Trigger-based Database Cleaning

Tests

End to end


						Feature: User permission

						  Background:
						   Given I create a user with id 504

						  Scenario:
							Given I log in with permission 'Users'
							When I call get 'users/view/504'
							Then I shall be granted access.

						  Scenario:
							Given I log in with permission 'Admin'
							When I call get 'users/view/504'
							Then I shall be granted access.

						  Scenario:
							Given I log in with permission 'Foo'
							When I call get 'users/view/504'
							Then I shall be redirected.

						

Integration

Includes DB interactions

Unit


                    public function testUserPermission()
                    {
                      // Arrange
                       $user = UserFactory::make()->withPermission('Foo')->getEntity();

                       // Act
                       $act = $this->Users->hasPermission($user, 'Foo');

                       // Assert
                       $this->assertTrue($act);
                    }
                

Unit


                        public function dataProvider()
                        {
                           return [
                              ['Admin', true],
                              ['Guru',  true],
                              ['Foo',   true],
                              ['Bar',   false],
                           ];
                        }

                        /** @dataProvider dataProvider */
                        public function testUserHasPermissionFoo(
                           string $perm,
                           bool $expect
                        )
                        {
                           $user = UserFactory::make()->withPermission($perm)->getEntity();

                           $act = $this->Users->hasPermission($user, 'Foo');

                           $this->assertSame($expect, $act);
                        }
                        

Tests should be

Readable Book

Easy to writeTool

Run fastChrono

Why test?

  • Development tool

  • Clean code

  • Documentation

  • Code integrity

  • Saves time

CakePHP's ORM


                namespace App\Model\Table;

                use Cake\ORM\Table;

                class UsersTable extends Table
                {
                    public function initialize(array $config): void
                    {
                        $this->belongsToMany('UsersGroups');
                    }
                }
                


                namespace App\Model\Table;

                use Cake\ORM\Table;

                class UsersGroupsTable extends Table
                {
                    public function initialize(array $config): void
                    {
                        $this
                            ->belongsToMany('Users')
                            ->belongsToMany('Permissions');
                    }
                }
                


                namespace App\Model\Table;

                use Cake\ORM\Table;

                class PermissionsTable extends Table
                {
                    public function initialize(array $config): void
                    {
                        $this->belongsToMany('UsersGroups');
                    }
                }
                


                    // Retrieve all users, join permissions
                    $users = $this->Users->find()
                        ->contain('UsersGroups.Permissions')
                        ->toArray();
                


                    // Filter users by permissions, join permissions
                    $admins = $this->Users->find()
                        ->matching('UsersGroups.Permissions', function ($q) {
                            return $q->where(['Permissions.name' => 'Admin')
                        })
                        ->toArray();
                


                    // With Database access
                    public function hasPermission(int $userId, string $perm): bool
                    {
                      return $this->Users->find()
                        ->innerJoinWith('UsersGroups.Permissions', function ($q) use ($perm) {
                            return $q->where(['Permissions.name' => $perm)
                        })
                        ->where(['Users.id' => $userId])
                        ->count() > 0;
                    }
                


                    // Without Database access
                    public function hasPermission(User $user, string $perm): bool
                    {
                        return in_array(
                            $perm,
                            Hash::extract($user, 'users_groups.{n}.permissions.{n}.name')
                        )
                    }
                

CakePHP Fixture Factories


                    protected function initialize(): void
                    {
                      $this->getTable()->belongsToMany('UsersGroups');
                    }

                    protected function getRootTableRegistryName(): string
                    {
                      return 'Users';
                    }

                    protected function setDefaultTemplate(): void
                    {
                      $this->setDefaultData(function(Generator $faker) {
                        return [
                          'username' => $faker->username,
                          'email' => $faker->email,
                        ];
                      });
                    }
                

                    $user = UserFactory::make()->getEntity();

                    UserFactory::make(4)->getEntities();

                    UserFactory::make(4)->persist();

                    UserFactory::make(['username' => 'Foo'])->persist();

                    UserFactory::make([
                      ['username' => 'Foo'],
                      ['username' => 'Bar'],
                    ])->persist();
                

                    UserFactory::make(3)
                      ->with('UsersGroups.Permissions', ['name' => 'Foo'])
                      ->getEntity();

                    UserFactory::make()
                      ->with('UsersGroups.Permissions', [
                        ['name' => 'Foo'],
                        ['name' => 'Bar'],
                      ])
                      ->getEntity();

                    UserFactory::make()
                      ->admin()
                      ->getEntity();
                

                    UserFactory::make()
                      ->with('UsersGroups.Permissions',
                            PermissionFactory::make()->guru()
                      )
                      ->getEntity();

                    UserFactory::make()
                      ->with('UsersGroups.Permissions', [
                            PermissionFactory::make()->admin(),
                            PermissionFactory::make()->guru(),
                      ])
                      ->getEntity();
                

Unit test


							public function testUserPermission()
							{
							  // Arrange
							   $user = UserFactory::make()
								->with('UsersGroups.Permissions', ['name' => 'Foo'])
								->getEntity();

							   // Act
							   $act = $this->Users->hasPermission($user, 'Foo');

							   // Assert
							   $this->assertTrue($act);
							}
						

Book Tool Chrono

Unit tests


						public function dataProviderForTestPermission()
						{
						   return [['Admin',true],['Guru',true],['Foo',true],['Bar',false]];
						}

						/** @dataProvider dataProviderForTestPermission */
						public function testPermission(string $p, bool $exp)
						{
						   $user = UserFactory::make()->withPermission($p)->getEntity();

						   $act = $this->Users->hasPermission($user, 'Foo');

						   $this->assertSame($exp, $act);
						}
						

Behavioral test


						Feature: User permission

						  Background:
						   Given I create a user with id 504

						  Scenario:
							Given I log in with permission 'Users'
							When I call get 'users/view/504'
							Then I shall be granted access.

						  Scenario:
							Given I log in with permission 'Admin'
							When I call get 'users/view/504'
							Then I shall be granted access.

						  Scenario:
							Given I log in with permission 'Guru'
							When I call get 'users/view/504'
							Then I shall be granted access.

						  Scenario:
							Given I log in with permission 'Foo'
							When I call get 'users/view/504'
							Then I shall be redirected.

						

Persisting costs

Time (Y axis) vs Number of tests (X axis)

CakePHP Fixture Factories

Default template

Association builder

Persist or not

Random Ids

Framework agnostic

DB cleaning

Transactions


Conclusion

Grow solid with the CakePHP Fixture Factories
vierge-noire/cakephp-fixture-factories

Join us to extend the trigger-based database cleaner
vierge-noire/test-database-cleaner

Credits: Juan Pablo Ramirez - Nicolas Masson