CakePHP Fixture Factories

Created by Nicolas Masson and Juan Pablo Ramirez
Press S for speaker view
vierge.noire.info@gmail.com

Static Test Fixtures are not fun

CakePHP fixtures are static

CakePHP Fixture Factories are dynamic

Example
User permissions

UsersTable ↔ UsersGroupsTable ↔ PermissionsTable

5 Tables


                            users
                            users_users_groups
                            users_groups
                            users_groups_permissions
                            permissions
                        

Static


							public function testUserPermission()
							{
							   $user = $this->Users->get(45, [
						         'contain' => ['UsersGroups.Permissions']
						   ])

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

							   $this->assertTrue($act);
							}
						

Dynamic


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

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

							   $this->assertTrue($act);
							}
						

Tests should be

Readable

Book

Easy to write

Tool

Fast

Chrono

Why test?

  • Clean code

  • Documentation

  • Code integrity

  • Saves time

Tests are the roots of your code

Test anatomy

Arrange

Act

Assert

Test permissions Foo

Arrange
Create user, user group, permission Foo, pivot tables

Act
Check permission Foo for user

Assert
Expect user to have permission Foo

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 dataProviderForUserWithPermissionFoo()
						{
						   return [
						      ['Admin', true],
						      ['Guru',  true],
						      ['Foo',   true],
						      ['Bar',   false],
						   ];
						}

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

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

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

Integration 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.

						

Setup

Installation

CakePHP 4.x


                        composer require --dev vierge-noire/cakephp-fixture-factories "^2.0"
                        

CakePHP 3.x


    						composer require --dev vierge-noire/cakephp-fixture-factories "^1.0"
					    

Load the plugin


						   // In Application.php
						   ...
						   protected function bootstrapCli(): void
						   {
						      // Load more plugins here
						      $this->addPlugin('CakephpFixtureFactories');
						   }
						

Update the listeners in phpunit.xml


							bin/cake fixture_factories_setup
						

Bake your factories


							bin/cake bake fixture_factory -a
						

Migrations (optional)

In tests/bootstrap.php


						\CakephpTestMigrator\Migrator::migrate();
						

User Factory


							bin/cake bake fixture_factory -a
						


							namespace App\Test\Factory;

							use CakephpFixtureFactories\Factory\BaseFactory;
							use Faker\Generator;

							class UserFactory extends BaseFactory
							{
							   protected function getRootTableRegistryName(): string
							   {
								return "Users";
							   }

							   protected function setDefaultTemplate(): void
							   {
								$this->setDefaultData(function (Generator $faker) {
								   return [
									// set the model's default values
									// For example:
									// 'name' => $faker->lastName
								   ];
								});
							   }
							}
						


							namespace App\Test\Factory;

							use CakephpFixtureFactories\Factory\BaseFactory;
							use Faker\Generator;

							class UserFactory extends BaseFactory
							{
							   protected function getRootTableRegistryName(): string
							   {
								return "Users";
							   }

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

							   public function withPermission(string $permission): self
							   {
								return $this->with('UserGroups.Permissions', [
								  'name' => $permission
								]);
							   }

							   public function admin(): self
							   {
								return $this->withPermission('Admin');
							   }

							   public function inFantasticMood(): self
							   {
								// Make this user happy here
								return $this;
							   }
							}
						

Tool

Examples


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

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

						UserFactory::make(3);

						UserFactory::make(['username' => 'Foo'], 2);

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

						UserFactory::make()->with('UserGroups', [
						  'name' => 'Golfers'
						]);

						UserFactory::make()->with(
						  'UserGroups',
						  UserGroupFactory::make(['name' => 'Golfers'])
						);

						UserFactory::make()->with('UserGroups', [
						  ['name' => 'Golfers'],
						  ['name' => 'Swimmers'],
						]);

						UserFactory::make(3)->with('UserGroups[5].Permissions');
					

Book Tool

Test Life Cycle

tests/Bootstrap

Run migrations

Book Tool

Test Starts

Truncate Dirty Tables

Chrono

Performance

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

->Pilot study here<-

Compatibility

CakePHP 3

CakePHP 4

Static fixtures

Mysql - Sqlite - PostgreSQL

Conclusion


						composer require --dev vierge-noire/cakephp-fixture-factories
					

https://github.com/vierge-noire/cakephp-fixture-factories
Go to the presentation
vierge.noire.info@gmail.com

Acknowledgements

Zuluru - Bancer - Dreamingmind

The CakePHP community!