Giter VIP home page Giter VIP logo

cakephp-fixture-factories's People

Contributors

ckbx-cakebox avatar dereuromark avatar dreamingmind avatar erwane avatar ishanvyas22 avatar jsm222 avatar liqueurdetoile avatar lordsimal avatar mbamarante avatar molbiounige avatar o0h avatar pabloelcolombiano avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

cakephp-fixture-factories's Issues

Problem with listeningToModelEvents([ ])

Using the test code:

$LocationsTable = LocationFactory::make()
    ->listeningToModelEvents(
        [
            'Model.beforeSave' => 'beforeSave',
        ]
    )
    ->persist();

the event is failing to run.

In an attempt to find the problem, I looked at the EventCollector after the listeningToModelEvents() method ran and saw this:

object(CakephpFixtureFactories\Factory\EventCollector) id:0 {
  private listeningBehaviors => [
    (int) 0 => 'Timestamp'
  ]
  private listeningModelEvents => [
    'Model.beforeSave' => 'beforeSave'
  ]
  private defaultListeningBehaviors => [
    (int) 0 => 'Timestamp'
  ]
  private factory => object(App\Test\Factory\LocationFactory) id:1 {
    protected applyListenersAndBehaviors => false
    protected marshallerOptions => [
      'validate' => false,
      'forceNew' => true
    ]
    protected saveOptions => [
      'checkRules' => false,
      'atomic' => false,
      'checkExisting' => false
    ]
    protected withModelEvents => false
  }
  private rootTableRegistryName => 'Locations'
}

The last property of the LocationFactory reads protected withModelEvents => false.

Based on this information I set LocationFactory::withModelEvents to true and the events ran properly. With this property set it does not matter whether I use the method listeningToModelEvents().

Either the code is wrong or this section of the documentation is wrong.

Issues with 'with()'

I have models: Customers, Invoices and DeliveryAddresses

CustomersTable.php:

 public function initialize(array $config): void
    {
        $this->hasMany('Invoices', [
            'foreignKey' => 'customer_id',
        ]);
    }

InvoicesTable.php:

    public function initialize(array $config): void
    {
        $this->belongsTo('Customers', [
            'foreignKey' => 'customer_id',
            'joinType' => 'INNER',
        ]);
        $this->belongsTo('DeliveryAddresses', [
            'foreignKey' => 'delivery_address_id',
        ]);
    }

DeliveryAddressesTable.php

    public function initialize(array $config): void
    {
        $this->hasOne('Invoices', [
            'foreignKey' => 'delivery_address_id',
        ]);
    }

I baked my factories as in the docs

In one of my test cases, when i do:

CustomerFactory::make()->withInvoices()->persist();

I get both models saved correctly

However if i do:

InvoiceFactory::make()->withCustomers()->persist();

I get the following error:

CakephpFixtureFactories\Error\PersistenceException : Error in Factory App\Test\Factory\InvoiceFactory.
Message: SQLSTATE[HY000]: General error: 1364 Field 'customer_id' doesn't have a default value

What am i doing wrong?

Another issue is when i try to make a deeper association.
As in the tutorial

CustomerFactory::make()->with('Invoices.DeliveryAddresses')->persist();

The Customer and Invoice are correctly saved. However delivery_address_id can be NULL, so the DeliveryAddress record is not created, and delivery_address_id in Invoice is NULL. When i force it to NOT NULL in database, i get the following error:

CakephpFixtureFactories\Error\PersistenceException : Error in Factory App\Test\Factory\CustomerFactory.
Message: SQLSTATE[HY000]: General error: 1364 Field 'delivery_address_id' doesn't have a default value

How do i make this work?
Thanks in advance!

Allow Entities and Array of Entities

For the moment, the make method allows only arrays, callable, and int.

This should be extended to entity interfaces and arrays of entity interfaces.

Important is that when performing a ->toArray() on an entity, hidden fields are not "rendered" in the array.

So performing a ->toArray() will not do, unless we call ->setHidden([]) prior to the ->toArray().

If you take a look at the Article.php file, there is extra a hidden array to test that this case is covered. This should be covered by the tests and the implementation.

I propose to create a new TestCase\Factory\BaseFactoryMakeWithEntity.php or so class with the tests covering this new feature.

Don't inflect associations property names

In DataCompiler::mergeWithToOne Inflector::singularize is used.
I think most of the time is already in singular mode, and calling singularize on singular mode is not recomended
Even if you setup a custom property, inflecting could change it, leading to an error.

My case

My project using spanish inflector rules. I have Materiales belongsTo Categorias using categoria as property name (inflected).
In DataCompiler::mergeWithToOne is inflected to categorium causing a foreign key to fail.

If this looks reasonable I can try making a PR

Need a way to NOT generate the Identity Key

The database has a sequence to generate the identities and will enable values to be set by the sequence. I am getting substantial instability through the fixture factories generating a primary key.

In previous vanilla fixtures I simply omitted the IDs in the data array and allowed the Postgres DB to generate the primary key. While there is a mechanism to make primary keys in the data, the function of it is unstable.

PDOException : SQLSTATE[42P01]: Undefined table: 7 ERROR:  relation "user_states_id_seq" does not exist
LINE 1: SELECT setval('user_states_id_seq', 1335968403);

I tried using the instance config trait to introduce a BaseFactory config parameter, which could then be used to bypass the line below in the DataCompiler, however there is a second fresh BaseFactory generated, which does not get passed the config parameter and therefore defaults to generating the primary key.

return $this->setPrimaryKey($entity);

Could this be a parameter on the persist function?

Set locale for faker

Is there any way to set locale for faker?

I saw Factory/BaseFactory.php, it seems don't care about locale.

    /**
     * @return \Faker\Generator
     */
    public function getFaker(): Generator
    {
        if (is_null(self::$faker)) {
            $faker = Factory::create();
            $faker->seed(1234);
            self::$faker = $faker;
        }

        return self::$faker;
    }

To use other locale, I should extend this method to create localized faker so far?

After installation, running test produces sqlite error

I've followed the install instructions successfully. Composer ran fine, plugin loaded in Application.php, listener added to phpunit.xml. I did not however do the steps related to migrations as this appears optional. Cake version 4.0.7

After installation I am able to bake the factories without error.

Running tests produces:

Dons-MacBook-Pro:ampfg4 dondrake$ vendor/bin/phpunit
PHPUnit 8.5.4 by Sebastian Bergmann and contributors.

Exception: SQLSTATE[HY000]: General error: 1 no such table: sqlite_sequence
In [/Library/WebServer/Documents/ampfg4/vendor/cakephp/cakephp/src/Database/Driver/Sqlite.php, line 120]

I'm using a mysql db, not sqlite.

If I remove the listener from the xml I can successfully run my tests. I can even make very limited use of the factories but truncation seems to be out of commission. Perhaps that is what the listeners are managing?

If I add the line regarding migrations to the bootstrap, the result is the same.

I did debug the value returned by the FixtureManager::getConnection() and it seems to be returning my expected mysql configuration settings.

Problem with contained entities

I have un-edited baked factories which work properly except the associated data is in the result as an array rather than an Entity.

Using this code:

        $record = WarehouseFactory::make(1)
            ->withOfficeAddress()
            ->persist();

I get this result. Notice that the office_address property which I've marked with > is an array rather than an Entity\OfficeAddress

'App\Model\Entity\Warehouse::__set_state(array(

  **omitted properties**

   '_fields' => 
  array (
    'name' => 'Kunze, Mueller and Dibbert',
    'token' => '57357f37-0ea3-38e5-8a6c-9de9d06e75fd',
    'active' => 1,
>   'office_address' => 
    array (
    ),
    'created' => 
    Cake\I18n\FrozenTime::__set_state(array(
       'date' => '2020-06-22 22:25:15.993353',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
    'modified' => 
    Cake\I18n\FrozenTime::__set_state(array(
       'date' => '2020-06-22 22:25:15.994147',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
    'id' => 1,
  ),

   **omitted properties**

))

As an alternative I used the Cake native tables to retrieve the result.

        WarehouseFactory::make(1)
            ->withOfficeAddress()
            ->persist();
        
        $ware = TableRegistry::getTableLocator()->get('Warehouses');
        $records = $ware
            ->find('all')
            ->contain('OfficeAddress')
            ->toArray();

In this case I have a problem because an Inflection rule I have written to take care of the word 'Address/Addresses' is not being run.

'App\Model\Entity\Warehouse::__set_state(array(

  **omitted properties**

   '_fields' => 
  array (
    'id' => 1,
    'name' => 'Kunze, Mueller and Dibbert',
    'token' => '57357f37-0ea3-38e5-8a6c-9de9d06e75fd',
    'active' => 1,
    'created' => 
    Cake\I18n\FrozenTime::__set_state(array(
       'date' => '2020-06-22 22:28:08.000000',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
    'modified' => 
    Cake\I18n\FrozenTime::__set_state(array(
       'date' => '2020-06-22 22:28:08.000000',
       'timezone_type' => 3,
       'timezone' => 'UTC',
    )),
>   'office_addres' => NULL,
  ),

  **omitted properties**

))

I have been able to confirm that using Cake's tables for the find() returns contained hasMany data properly. I have not confirmed that hasOne works properly.

Regarding the Inflection rule, my rule is written in the bootstrap method of Application.php. (I resolved this by putting the rule in test/bootstrap)

Template file `No bake template found for "fixture_factory" skipping file generation.` could not be found

What is the error?

bin/cake bake fixture_factory -a -f
Exception: Template file `No bake template found for "fixture_factory" skipping file generation.` could not be found.
In [No bake template found for "fixture_factory" skipping file generation., line 104]

2020-05-01 11:19:41 Error: [Cake\View\Exception\MissingTemplateException] Template file `No bake template found for "fixture_factory" skipping file generation.` could not be found. in No bake template found for "fixture_factory" skipping file generation. on line 104
Exception Attributes: array (
  'file' => 'No bake template found for "fixture_factory" skipping file generation.',
  'paths' => 
  array (
  ),
)
Stack Trace:
- <my-path>/vendor/pakacuda/cakephp-fixture-factories/src/Command/FixtureFactoryCommand.php:198
- <my-path>/vendor/pakacuda/cakephp-fixture-factories/src/Command/FixtureFactoryCommand.php:139
- <my-path>/vendor/pakacuda/cakephp-fixture-factories/src/Command/FixtureFactoryCommand.php:168
- <my-path>/vendor/cakephp/cakephp/src/Console/BaseCommand.php:175
- <my-path>/vendor/cakephp/cakephp/src/Console/CommandRunner.php:336
- <my-path>/vendor/cakephp/cakephp/src/Console/CommandRunner.php:171
- <my-path>/bin/cake.php:12

What did I do?

composer require --dev pakacuda/cakephp-fixture-factories  

This successfully added the plugin to the vendor

composer require --dev pakacuda/cakephp-fixture-factories    
Using version ^0.2.4 for pakacuda/cakephp-fixture-factories
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 2 installs, 0 updates, 0 removals
  - Installing fzaninotto/faker (v1.9.1): Downloading (100%)         
  - Installing pakacuda/cakephp-fixture-factories (v0.2.4): Downloading (100%)         
Writing lock file
Generating autoload files
> Cake\Composer\Installer\PluginInstaller::postAutoloadDump
17 packages you are using are looking for funding.
Use the `composer fund` command to find out more!

Then i manually added the Plugin into the bootstrapCli() function in the src/Application.php

$this->addPlugin('CakephpFixtureFactories');

After that I executed

bin/cake bake fixture_factory -a -f   

For me it seems like it can't find the vendor/pakacuda/cakephp-fixture-factories/templates/bake/fixture_factory.twig even though it is present.

My composer.json
{
  "name": "cakephp/app",
  "description": "CakePHP skeleton app",
  "homepage": "https://cakephp.org",
  "type": "project",
  "license": "MIT",
  "require": {
    "php": ">=7.2",
    "ext-curl": "*",
    "ext-ftp": "*",
    "ext-json": "*",
    "cakephp/cakephp": "^4.0",
    "cakephp/migrations": "3.0.0-beta2",
    "cakephp/plugin-installer": "^1.0",
    "gamez/mite": "^1.0",
    "guzzlehttp/guzzle": "^6.5",
    "jeroendesloovere/vcard": "^1.7",
    "mobiledetect/mobiledetectlib": "2.*",
    "phan/phan": "^2.4",
    "phpoffice/phpspreadsheet": "^1.9"
  },
  "require-dev": {
    "cakephp/bake": "^2.0",
    "cakephp/cakephp-codesniffer": "dev-next",
    "cakephp/debug_kit": "^4.0",
    "dereuromark/cakephp-ide-helper": "^1.5",
    "easybill/php-sdk": "^2.0",
    "josegonzalez/dotenv": "3.*",
    "pakacuda/cakephp-fixture-factories": "^0.2.4",
    "phpseclib/phpseclib": "^2.0",
    "phpunit/phpunit": "^8.0",
    "psy/psysh": "@stable"
  },
  "suggest": {
    "markstory/asset_compress": "An asset compression plugin which provides file concatenation and a flexible filter system for preprocessing and minification.",
    "dereuromark/cakephp-ide-helper": "After baking your code, this keeps your annotations in sync with the code evolving from there on for maximum IDE and PHPStan compatibility."
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  },
  "autoload-dev": {
    "psr-4": {
      "App\\Test\\": "tests/",
      "Cake\\Test\\": "vendor/cakephp/cakephp/tests/"
    }
  },
  "scripts": {
    "post-install-cmd": "App\\Console\\Installer::postInstall",
    "post-create-project-cmd": "App\\Console\\Installer::postInstall",
    "post-autoload-dump": "Cake\\Composer\\Installer\\PluginInstaller::postAutoloadDump",
    "check": [
      "@test",
      "@cs-check"
    ],
    "cs-check": "phpcs --colors -p --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
    "cs-fix": "phpcbf --colors --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/",
    "stan": "phpstan analyse src/",
    "stan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan-shim:^0.11 && mv composer.backup composer.json",
    "test": "phpunit --colors=always"
  },
  "prefer-stable": true,
  "config": {
    "sort-packages": true
  },
  "minimum-stability": "dev"
}
My phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
  colors="true"
  bootstrap="tests/bootstrap.php"
  backupGlobals="true"
>

  <testsuites>
    <testsuite name="cakephp">
      <directory>tests/TestCase/</directory>
      <!-- Excludes are required in order to let DatabaseSuite decorate the tests -->
      <exclude>tests/TestCase/Database/</exclude>
      <exclude>tests/TestCase/ORM/</exclude>
    </testsuite>
    <testsuite name="database">
      <file>tests/TestCase/DatabaseSuite.php</file>
    </testsuite>
  </testsuites>

  <listeners>
    <listener class="Cake\TestSuite\Fixture\FixtureInjector">
      <arguments>
        <object class="Cake\TestSuite\Fixture\FixtureManager"/>
      </arguments>
    </listener>
  </listeners>

  <!-- Setup a listener for fixtures -->
  <listeners>
    <listener class="CakephpFixtureFactories\TestSuite\FixtureInjector">
      <arguments>
        <object class="CakephpFixtureFactories\TestSuite\FixtureManager" />
      </arguments>
    </listener>
  </listeners>

  <!-- Prevent coverage reports from looking in tests, vendors, config folders -->
  <filter>
    <whitelist>
      <directory suffix=".php">src/</directory>
      <exclude>
        <!--
        This file contains a few functions that cannot be tested
        as they contain die; or breakpoint functionality.
        -->
        <file>src/basics.php</file>
        <!-- This file is deprecated and unused -->
        <file>src/Core/ClassLoader.php</file>
      </exclude>
    </whitelist>
  </filter>

  <php>
    <ini name="memory_limit" value="-1"/>
    <!-- E_ALL & ~E_USER_DEPRECATED (16383)-->
    <!-- E_ALL (32767) -->
    <ini name="error_reporting" value="32767"/>

    <!-- SQLite
    <env name="DB_DSN" value="sqlite:///:memory:"/>
    -->
    <!-- Postgres
    <env name="DB_DSN" value="postgres://localhost/cake_test?timezone=UTC"/>
    -->
    <!-- Mysql
    <env name="DB_DSN" value="mysql://localhost/cake_test?timezone=UTC"/>
    -->
    <!-- SQL Server
    <env name="DB_DSN" value="sqlserver://localhost/cake_test?timezone=UTC"/>
    -->
  </php>
</phpunit>

Add a way to define a seed for faker

It could be done dynamically on a 'per test' basis in the setUp method of a phpunit test case
Or maybe through a config file. Using this method would seed the whole test suite.
Will have to see which option is the best suited. I would go for the first one alogside a 'default' seed

Field names must be quoted or users must pay attentions to it during use persist function?

Tables having field names that using keyword like 'key' could not be persisted.
Error Message: Message: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'key, value) VALUES ...

To avoid this quote field name in setDefaultTemplate function like below.

    protected function setDefaultTemplate(): void
    {
        $this->setDefaultData(function (Generator $faker) {
            return [
                '`key`' => $faker->word(),
                '`value`' => $faker->randomNumber(),
            ];
        });
    }

The message was throwed at vendor/vierge-noire/cakephp-fixture-factories/src/Factory/BaseFactory.php:334

The persist method must quote as long as it is not virtual field.
But it seems responsibility of CakePHP itself?

I know it's our code's fault, but sometime we must maintain kind of this type project...
I would appreciate if it was fixed, but we can avoid like above so just let me register issue.

Composite primary key

Hello,

I have a schema with a composite primary key. When calling BaseFactory::persist(), DataCompiler returns a TypeError DataCompiler::getRootTablePrimaryKey() must be a string, array returned.

DataCompiler::getRootTablePrimaryKey() return type is set to string

    public function getRootTablePrimaryKey(): string
    {
        return $this->getFactory()->getRootTableRegistry()->getPrimaryKey();
    }

and is therefore incompatible with Cake\ORM\Table::getPrimaryKey() which returns string|string[]

Speed hit on tests that don't use the database

I've created this simple test class

<?php

namespace App\Test\TestCase\Policy;

class SimpleTest extends \Cake\TestSuite\TestCase
{
    public function testMe()
    {
        $this->assertTrue(true);
    }

}

And in Phinx\Db\Adapter\PdoAdapter::execute() I have this to see what is happening on the db:

Screen Shot 2020-06-01 at 10 27 37 AM

This is the unexpected result of running the test:

'START TRANSACTION
''COMMIT
''INSERT INTO `cakephp_fixture_factories_phinxlog` (`version`, `migration_name`, `start_time`, `end_time`, `breakpoint`) VALUES (\'20200512051455\', \'TruncateDirtyTables\', \'2020-06-01 17:22:34\', \'2020-06-01 17:22:34\', 0);
''START TRANSACTION
''COMMIT
''DELETE FROM `cakephp_fixture_factories_phinxlog` WHERE `version` = \'20200512051455\'
'
'START TRANSACTION
''COMMIT
''INSERT INTO `cakephp_fixture_factories_phinxlog` (`version`, `migration_name`, `start_time`, `end_time`, `breakpoint`) VALUES (\'20200512051455\', \'TruncateDirtyTables\', \'2020-06-01 17:22:36\', \'2020-06-01 17:22:36\', 0);
''START TRANSACTION
''COMMIT
''DELETE FROM `cakephp_fixture_factories_phinxlog` WHERE `version` = \'20200512051455\'
'
'START TRANSACTION
''COMMIT
''INSERT INTO `cakephp_fixture_factories_phinxlog` (`version`, `migration_name`, `start_time`, `end_time`, `breakpoint`) VALUES (\'20200512051455\', \'TruncateDirtyTables\', \'2020-06-01 17:22:38\', \'2020-06-01 17:22:38\', 0);
''START TRANSACTION
''COMMIT
''DELETE FROM `cakephp_fixture_factories_phinxlog` WHERE `version` = \'20200512051455\'
'


Time: 6.3 seconds, Memory: 8.00 MB

OK (1 test, 1 assertion)
Process finished with exit code 0

Handle the branch switching issue on migration

Since the FixtureManager truncates all tables excluding phinxlog, the schema needs to be updatable after switching to a different git branch.

One possibility is to add the option (somewhere to be defined) to truncate the phinxlog as well, so the schema may be rebuild from scratch.

This issue is the same for the test DB as well as for the normal DB. We could search for a general solution to rollback / migrate the migrations from one branch to the other.

[Feature request] - Add a `FactoryAwareTrait` to ease getting factory

Hi,

I've created for myself a little helper trait to ease getting a factory and I'm wondering if it may be of some interest to include one in this package.

Instead of doing

<?php
use App\Tests\Factory\FooFactory;
use App\Tests\Factory\BarFactory;
use App\Tests\Factory\BazFactory;

class MyTest extends TestCase
{
     public function testFoo(): void
     {
        FooFactory::make()->...;
     }

     public function testBar(): void
     {
        BarFactory::make()->...;
     }

     public function testBaz(): void
     {
        BazFactory::make()->...;
     }
}

I'm doing this :

<?php
use App\Tests\FactoryAwareTrait;

class MyTest extends TestCase
{
    use FactoryAwareTrait;
    
    public function testFoo(): void
    {
      $this->getFactory('foo')->...;
    }

    public function testBar(): void
    {
      $this->getFactory('bars')->...;
    }

     public function testBaz(): void
     {
        $this->getFactory('Bazes')->...;
     }
}

I've added some string transforms to be tolerant against cases and plurals. I've also extended my base test case to include trait so getFactory is available everywhere ๐Ÿ˜„

If you're interested, here's the code or I can submit a PR

<?php
declare(strict_types=1);

namespace App\Tests

use Cake\Core\Configure;
use Cake\Utility\Inflector;
use CakephpFixtureFactories\Factory\BaseFactory;

trait FactoryAwareTrait
{
    public function getFactory(string $model): BaseFactory
    {
        // Build class name
        $app = Configure::read('App.namespace') ?? 'App';
        $model = Inflector::singularize(ucfirst(strtolower($model)));
        $className = "$app\\Test\\Factory\\$model" . "Factory";

        if (class_exists($className)) {
            return $className::make();
        }

        throw new \Exception('Unable to locate Factory class for ' . $model);
    }
}

Hidden properties will not be persisted

Given a model with hidden properties, when a factory generates an entity, the hidden properties will not be persisted.

Generally, the package should be completely blind to hidden properties. In other words, all entities created should have their hidden properties ignored.

Add a way to use the new fixture factories alongside old fixture factories

People working on a project that already has cakephp fixtures should have a way to use the factories without rewriting all their fixtures first.
Because the FixtureInjector and FixtureManager provided with this package don't offer a way to deal with both kind of fixtures, this could repel many people from giving it a try and also to just let them progressively convert their project to use the fixture factories.

Enhance Scenarios

ScenarioAwareTrait::loadFixtureScenario() should:

  • have the possibility to pass arguments when loading
  • return the arguments of the FixtureScenarioInterface::load() method

makeWithModelListenersAndBehaviors should be a transient state change, not permanent

makeWithModelListenersAndBehaviors as currently implemented sets the applyListenersAndBehaviors property of the factory, and leaves it set. I have two concerns with this: one philosophical and one practical.

The philosophical issue is that the function name does not reflect that it will be permanently changing the state. A name like enableModelListenersAndBehaviorsAndMake would be clearer about this. But secondly, modern design principles prefer that functions not have two effects; if you need to use "and" to describe what a function does, it should generally be split into two functions.

The practical issue is that if I have testA and testB, where testA calls makeWithModelListenersAndBehaviors but testB uses only make, then the behaviour of testB will depend on whether testA was run before it or not, because it's not just the concrete factory object that's affected by this, but a static property that make references.

Test fixture scenarii

Feature the possibility to create scenarii to persist a set of test fixtures.

The scenario would be loaded at will in the setUp or at the test level to create conveniently a large set of data.

Scenarios should implement a TestFixtureScenario interface or abstract class requiring an execute() method where the developer creates all her test fixtures with the help of fixture factories.

Typically, in a test, the developer can call $this->loadScenario(FullyQualified\Name\Scenario::class) using the FixtureScenarioAwareTrait provided by the present task.

By convention, the scenarios are located in the tests/FixtureScenario directory. However, since these are called by fully qualified name, the developer is free to place them wherever at convenience.

Ultimately, scenarii could be loaded:

  • in tests (e.g. setUp()) to generate a set of fixtures,
  • on the command line to provide a large set of test data to perform testing on the browser (this will be implemented in an additional task).

Cleanup the main directory

The following files should be placed in the tests directory or elsewhere in order to clean of the main directory:

  • .env files
  • phpcs.xml.dist
  • phpstan.neon
  • run_tests.sh

Doc corrections

It concerns docs/setup.md

To ignore connections the key should be TestSuiteLightIgnoredConnections instead of TestFixtureIgnoredConnections as such:

<?php

return [   
    'TestSuiteLightIgnoredConnections' => [
        'test_foo_connection_to_be_ignored',
        'test_bar_connection_to_be_ignored',
        ...
    ],
];

and, for people like me, I would also add some instruction on how to load fixture_factories.php as this was not obvious for me.

// in config/bootstrap.php
Configure::load('fixture_factories', 'default')

Type of class (e.g abstract) should be detected

In my app I have some abstract Table classes.

When I run bake fixture_factory --all I get an error because plugin tries to instantiate those classes.

It would be nice if it could detect and exclude those classes.

How to reproduce this error:

  • create abstract Table class in /src/ModelTable directory
  • run bin/cake bake fixture_factory --all

Cannot truncate because of foreign key constraint

I have a test that passes, but during the cleanup process throws this error:

Exception: SQLSTATE[42000]: Syntax error or access violation: 1701 Cannot truncate a table referenced in a foreign key constraint (`fg4_test`.`addresses`, CONSTRAINT `addresses_ibfk_4` FOREIGN KEY (`warehouse_office_id`) REFERENCES `fg4_test`.`warehouses` (`id`))
In [/Library/WebServer/Documents/ampfg4/vendor/robmorgan/phinx/src/Phinx/Db/Adapter/PdoAdapter.php, line 188]

There is indeed a foreign key referencing warehouses on the addresses table. Is there something else I should be doing in my test or elsewhere to inform the system of the relationship?

This is my test:

    public function testExample()
    {
        WarehouseFactory::make(1)->persist();
        $warehouse = TableRegistry::getTableLocator()->get('Warehouses')
            ->get('1');
        $this->assertInstanceOf(Warehouse::class, $warehouse);

    }

This is my factory code:

    protected function setDefaultTemplate(): void
    {
        $this->setDefaultData(function(Generator $faker) {
            return [
                'name' => $faker->company(),
                'token' => $faker->uuid(),
                'active' => 1,
            ];
        });
    }

This is the sequence of sql commands that are running:

'START TRANSACTION
''COMMIT
''INSERT INTO `cakephp_fixture_factories_phinxlog` (`version`, `migration_name`, `start_time`, `end_time`, `breakpoint`) VALUES (\'20200512051455\', \'TruncateDirtyTables\', \'2020-06-01 19:04:47\', \'2020-06-01 19:04:48\', 0);
''START TRANSACTION
''COMMIT
''DELETE FROM `cakephp_fixture_factories_phinxlog` WHERE `version` = \'20200512051455\'
'
'START TRANSACTION
''TRUNCATE TABLE `warehouses`

I'm on v0.2.13

I've tried this by baking both

bin/cake bake fixture_factory warehouses -f -m

and

bin/cake bake fixture_factory warehouses -f

I also baked the linked addresses table using the -m option

and I've both included and excluded the test/boostrap Migrator::migrate()

Work with entities instead of arrays

Related to #43

Since we are short on time before the next release, I propose the following implementation to have the package working internally with entities, rather than with arrays.

Field 'foo' doesn't have a default value

After updating I started getting 'Field 'foo' doesn't have a default value' errors on previously passing tests.

Wasn't the system glossing over errors like this when make()ing records?

After updating to 0.2.14 (that's when I noticed it), tests that were previously running started throwing this error.

My code was in a bit of chaos at that point so I'm not 100% sure about this.

I'm on 0.2.15 now

Persist with multiple linked entities

Hi there :)

I was writing some tests and one of them wasn't working.

I found it was due to how i was making my factory. I thought that i was able to chaine 'with' method like this :

OrderFactory::make(2)->with('OrdersProducts[5].Products', ['done' => true])->with('ShippingServices')->persist();

But no. It will make 4 Orders : 2 with Products and 2 with ShippingServices.

So i search in the doc how to do this, but i didn't find a exemple that suit me.

Do you have a nice way to do this ?

Thanks a lot :)

Have a good day

Create a findOrMake() method

This method should search in the DB for an entity matching the data provided.

If not found, this entity should be created.

This method returns an entity or an array of entities.

Add the possibility to attach entities that will always have the same fixed primary key

Motivation :

A test scenario might require the usage of fixtures with fixed primary keys.
For example, one might expect to have one or more entities's association to be associated to the same entities.
With the current implementation, it is difficult to achieve such scenarios with ease and without worrying about primary key collisions.
This feature will allow to associate entities to other ones that will always have the same primary keys.

The behavior of the current API MUST NOT be impacted by the changes in this feature. All current tests MUST NOT be modified and MUST pass.
Additional tests covering the new feature must be added.
The enhancement consists of adding an 'attach' method to the BaseFactory class.
The attach method will support 3 types of parameters.

  1. A factory object
  2. An entity that has already been persisted
  3. An array of entities where each entity has already been persisted

Create a contribution guideline

The package being compatible with CakePHP 3 and 4, it has several branches.

Among other details for the contributor, this should be explained in a dedicated contribution page in the docs.

Lifecycle callbacks not running on a model

I have a model with a beforeSave() callback which is not being run.

Here is the relevant setup in my table:

class LocationsTable extends Table
{

    /**
     * @var string[]
     */
    protected $digestColumns = [
        'loc_string' ,
        'zone_id' ,
    ];

    public function implementedEvents(): array
    {
        return
            [
                'Model.afterMarshal' => 'afterMarshal',
                'Model.beforeSave' => 'beforeSave'
            ];
    }

    /**
     * Set the digest value for locations
     *
     * Location stacks need to discover all the location records that refer
     * to a single location (each marking storage of a separate item).
     * A digest value derived from the zone id and location json is used.
     * a virtual field gets the json data as a string for the digest process
     *
     * @param EventInterface $event
     * @param EntityInterface $entity
     * @param ArrayObject $options
     * @return bool
     */
    public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
    {
        $entity->digest = BusinessRules::digest($entity, $this->digestColumns);

        return true;
    }
}

I understand some save options and marshaling options are off by default and can be turned back on. But I don't see any mention of the callbacks...

Perhaps I'm missing something obvious?

The callback does work in the production code.

Open table truncators

The branch truncator offers the possibility to add one's own table truncator by creating a fixture-factories.php config file and adding the truncator(s) as follows:

<?php

return [
    'TableTruncators' => [
        '\someTestDriver' => '\someTestTruncator'
    ]
];

Fixture factories persist at the command line

This feature will enable to pass a string of PHP code in the command line to create factories.
Example:
bin/cake fixture_factories_persist User -n 5 -m active
will populate the test connection with 5 users as defined by the factory passed in argument.

bin/cake fixture_factories_persist User works
bin/cake fixture_factories_persist Users works

The connection is set to test per default, but may be overwritten with the option --connection (-c).
An option --dry-run (-d) will show the created entities in the command prompt without persisting.
An option --plugin (-p) will fetch the factory in a plugin.
An option --number (-n) will persist n fixtures.
An option --connection (-c) will specify the connection name (default is test).
An option --method (-m) will apply the method name provided before persisting.
Example: assuming the UserFacory has a method admin(), -m admin will call the method admin() to enable the creation of a user with administrator rights.

Issue when working with relationship data

I have two tables:

src/Model/ProductsTable.php

<?php
declare(strict_types=1);

namespace App\Model\Table;

...

class ProductsTable extends Table
{
    public function initialize(array $config): void
    {
        ...

        $this->belongsTo('Categories', [
            'foreignKey' => 'category_id',
            'joinType' => 'INNER',
        ]);
    }

    ...
}

src/Model/CategoriesTable.php

<?php
declare(strict_types=1);

namespace App\Model\Table;

...

class CategoriesTable extends Table
{
    public function initialize(array $config): void
    {
        ...

        $this->belongsTo('ParentCategories', [
            'className' => 'Categories',
            'foreignKey' => 'parent_id',
        ]);
        $this->hasMany('ChildCategories', [
            'className' => 'Categories',
            'foreignKey' => 'parent_id',
        ]);
        $this->hasMany('Products', [
            'foreignKey' => 'category_id',
        ]);
    }

    ...
}

Here's my factory files:

tests/Factory/ProductFactory.php

<?php
declare(strict_types=1);

namespace App\Test\Factory;

...

class ProductFactory extends CakephpBaseFactory
{
    protected function getRootTableRegistryName(): string
    {
        return 'Products';
    }

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

    public function withCategories(array $parameter = null): ProductFactory
    {
        return $this->with(
            'Categories',
            \App\Test\Factory\CategoryFactory::make($parameter)
        );
    }
}

tests/Factory/CategoryFactory.php

<?php
declare(strict_types=1);

namespace App\Test\Factory;

...

class CategoryFactory extends CakephpBaseFactory
{
    protected function getRootTableRegistryName(): string
    {
        return 'Categories';
    }

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

    public function withParentCategories(array $parameter = null): CategoryFactory
    {
        return $this->with(
            'ParentCategories',
            \App\Test\Factory\CategoryFactory::make($parameter)
        );
    }

    public function withChildCategories(array $parameter = null, int $n = 1): CategoryFactory
    {
        return $this->with(
            'ChildCategories',
            \App\Test\Factory\CategoryFactory::make($parameter, $n)->without('Category')
        );
    }

    public function withProducts(array $parameter = null, int $n = 1): CategoryFactory
    {
        return $this->with(
            'Products',
            \App\Test\Factory\ProductFactory::make($parameter, $n)->without('Category')
        );
    }
}

Now when I try to access product factory with categories, it doesn't seem to work:

$product = ProductFactory::make()->withCategories(1)->getEntity();

Above code returns below error:

TypeError: Argument 1 passed to App\Test\Factory\ProductFactory::withCategories() must be of the type array or null, integer given, called in /var/www/html/prod/cakephp4/tests/TestCase/Controller/ProductsControllerTest.php on line 26

I've just baked both factory and haven't changed anything. Also the code I'm using is documented here, which seems to be outdated may be.

To fix this I passed null in withCategories call, then it seems to work.

But when I add fake data and try to get those it doesn't return related category data in the returned entity result. I might be missing here something don't know but I can't find anything in the documentation to help me with this.

Please suggest the proper way to work with relationship data.

The behavior I want is, when we do ProductFactory::make()->withCategories(1)->getEntity() it should create new product entity along with a new category entity and the category entity should auto relate to product entity automatically without doing any more hustle.

Contribution guideline

The package being compatible with CakePHP 3 and 4, it has several branches.

Among other details for the contributor, this should be explained in a dedicated contribution page in the docs.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.