Skip to content

Commit

Permalink
#235 First draft of v3.1
Browse files Browse the repository at this point in the history
  • Loading branch information
pabloelcolombiano committed Nov 25, 2023
1 parent c22686f commit 446ec21
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 92 deletions.
23 changes: 23 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ In order to persist the data generated, use the method `persist` instead of `get
$articles = ArticleFactory::make(3)->persist();
```

You may want to retrieve your entities as a result set, allowing you conveniently query the entities created:
```php
$articles = ArticleFactory::make(3)->getResultSet(); // Will not persist in the DB
$articles = ArticleFactory::make(3)->getPersistedResultSet(); // Will persist in the DB
```

Do not forget to check the [plugin's tests](../tests) for
more insights!

Expand Down Expand Up @@ -137,3 +143,20 @@ $article = ArticleFactory::make([
// or
$article = ArticleFactory::make()->setField('array_field.key2', 'newValue')->getEntity();
```

### Mocking select queries

You might come across tests where you want to avoid the communication
with the database, and yet you would need to simulate the output of a select query.

For example in a `ArticlesIndexController` you want to emulate a query returning
10 articles and want to test that the rendering is made properly.

In your test, where `$this` is the TestCase extending [CakePHP's TestCase](https://book.cakephp.org/4/en/development/testing.html#mocking-model-methods):
```php
$articleFactory = ArticleFactory::make(10)->withAuthors();
\CakephpFixtureFactories\ORM\SelectQueryMocker::mock($this, $articleFactory);
```

Any select queries on the `ArticlesTable` will now return these 10 articles with their associations.
The queries themselves, involving the interaction with the DB, should be tested elsewhere.
17 changes: 2 additions & 15 deletions src/Plugin.php → src/CakephpFixtureFactoriesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,8 @@
use Cake\Core\BasePlugin;

/**
* Plugin class for migrations
* Plugin class for creating test fixtures
*/
class Plugin extends BasePlugin
class CakephpFixtureFactoriesPlugin extends BasePlugin
{
/**
* Plugin name.
*
* @var string|null
*/
protected ?string $name = 'CakephpFixtureFactories';

/**
* Don't try to load routes.
*
* @var bool
*/
protected bool $routesEnabled = false;
}
78 changes: 48 additions & 30 deletions src/Factory/AssociationBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
use Cake\ORM\Association\HasMany;
use Cake\ORM\Association\HasOne;
use Cake\ORM\Table;
use Cake\Utility\Hash;
use Cake\Utility\Inflector;
use CakephpFixtureFactories\Error\AssociationBuilderException;
use Exception;
Expand All @@ -37,7 +36,15 @@ class AssociationBuilder
getFactory as getFactoryInstance;
}

private array $associated = [];
/**
* @var array<\CakephpFixtureFactories\Factory\BaseFactory>
*/
private array $associations = [];

/**
* @var array
*/
private array $manualAssociations = [];

/**
* @var \CakephpFixtureFactories\Factory\BaseFactory
Expand Down Expand Up @@ -80,24 +87,6 @@ public function getAssociation(string $associationName): Association
}
}

/**
* Collect an associated factory to the BaseFactory
*
* @param string $associationName Association
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @return void
*/
public function collectAssociatedFactory(string $associationName, BaseFactory $factory): void
{
$associations = $this->getAssociated();

if (!in_array($associationName, $associations)) {
$associations[$associationName] = $factory->getMarshallerOptions();
}

$this->setAssociated($associations);
}

/**
* @param string $associationName Name of the association
* @param \CakephpFixtureFactories\Factory\BaseFactory $associationFactory Factory
Expand Down Expand Up @@ -256,29 +245,49 @@ public function associationIsToMany(Association $association): bool
*/
public function dropAssociation(string $associationName): void
{
$this->setAssociated(
Hash::remove(
$this->getAssociated(),
$associationName
)
);
$explode = explode('.', $associationName);
$baseAssociationName = array_shift($explode);
if (!isset($this->associations[$baseAssociationName])) {
return;
}
if (count($explode) === 0) {
unset($this->associations[$baseAssociationName]);
} else {
$this->associations[$baseAssociationName]->without(implode('.', $explode));
}
}

/**
* @return array
*/
public function getAssociated(): array
{
return $this->associated;
$result = [];
foreach ($this->associations as $name => $associatedFactory) {
$result[$name] = $associatedFactory->getMarshallerOptions();
}

return array_merge_recursive($result, $this->manualAssociations);
}

/**
* @return array<\CakephpFixtureFactories\Factory\BaseFactory>
*/
public function getAssociations(): array
{
return $this->associations;
}

/**
* @param array $associated Associations of the master factory
* Add an associated factory to the BaseFactory
*
* @param string $associationName Association
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory Factory
* @return void
*/
public function setAssociated(array $associated): void
public function addAssociation(string $associationName, BaseFactory $factory): void
{
$this->associated = $associated;
$this->associations[$associationName] = $factory;
}

/**
Expand All @@ -288,4 +297,13 @@ public function getTable(): Table
{
return $this->getFactory()->getTable();
}

/**
* @param array $associations
* @return void
*/
public function addManualAssociations(array $associations): void
{
$this->manualAssociations = array_merge_recursive($associations, $this->manualAssociations);
}
}
47 changes: 34 additions & 13 deletions src/Factory/BaseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Cake\Datasource\ResultSetInterface;
use Cake\I18n\I18n;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\ResultSet;
use Cake\ORM\Table;
use CakephpFixtureFactories\Error\FixtureFactoryException;
use CakephpFixtureFactories\Error\PersistenceException;
Expand Down Expand Up @@ -228,6 +229,7 @@ public function getFaker(): Generator
* Produce one entity from the present factory
*
* @return \Cake\Datasource\EntityInterface
* @deprecated Use getResultSet instead. Will be removed in v4.
*/
public function getEntity(): EntityInterface
{
Expand All @@ -238,29 +240,49 @@ public function getEntity(): EntityInterface
* Produce a set of entities from the present factory
*
* @return array<\Cake\Datasource\EntityInterface>
* @deprecated Use getResultSet instead. Will be removed in v4.
*/
public function getEntities(): array
{
return $this->toArray();
}

/**
* Creates a result set of non-persisted entities
*
* @return \Cake\ORM\ResultSet
*/
public function getResultSet(): ResultSet
{
return new ResultSet($this->toArray());
}

/**
* Creates a result set of persisted entities
*
* @return \Cake\ORM\ResultSet
*/
public function getPersistedResultSet(): ResultSet
{
return new ResultSet((array)$this->persist());
}

/**
* @return array
*/
public function getMarshallerOptions(): array
{
$associated = $this->getAssociationBuilder()->getAssociated();
if (!empty($associated)) {
return array_merge($this->marshallerOptions, [
'associated' => $this->getAssociationBuilder()->getAssociated(),
]);
return array_merge($this->marshallerOptions, compact('associated'));
} else {
return $this->marshallerOptions;
}
}

/**
* @return array
* @deprecated will be removed in v4
*/
public function getAssociated(): array
{
Expand Down Expand Up @@ -305,12 +327,16 @@ public function getTable(): Table
/**
* @return \Cake\Datasource\EntityInterface|\Cake\Datasource\ResultSetInterface|iterable<\Cake\Datasource\EntityInterface>
* @throws \CakephpFixtureFactories\Error\PersistenceException if the entity/entities could not be saved.
* @deprecated Use getPersistedResultSet. Will be removed in v4. Use getPersistedResultSet
*/
public function persist(): EntityInterface|iterable|ResultSetInterface
{
$this->getDataCompiler()->startPersistMode();
$entities = $this->toArray();
$this->getDataCompiler()->endPersistMode();
try {
$entities = $this->toArray();
} finally {
$this->getDataCompiler()->endPersistMode();
}

try {
if (count($entities) === 1) {
Expand All @@ -331,7 +357,7 @@ public function persist(): EntityInterface|iterable|ResultSetInterface
private function getSaveOptions(): array
{
return array_merge($this->saveOptions, [
'associated' => $this->getAssociated(),
'associated' => $this->getAssociationBuilder()->getAssociated(),
]);
}

Expand Down Expand Up @@ -550,7 +576,7 @@ public function with(string $associationName, array|int|callable|BaseFactory|Ent
$isToOne = $this->getAssociationBuilder()->processToOneAssociation($associationName, $factory);
$this->getDataCompiler()->collectAssociation($associationName, $factory, $isToOne);

$this->getAssociationBuilder()->collectAssociatedFactory($associationName, $factory);
$this->getAssociationBuilder()->addAssociation($associationName, $factory);

return $this;
}
Expand All @@ -576,12 +602,7 @@ public function without(string $association)
*/
public function mergeAssociated(array $data)
{
$this->getAssociationBuilder()->setAssociated(
array_merge(
$this->getAssociationBuilder()->getAssociated(),
$data
)
);
$this->getAssociationBuilder()->addManualAssociations($data);

return $this;
}
Expand Down
70 changes: 70 additions & 0 deletions src/ORM/SelectQueryMocker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);

/**
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) 2020 Juan Pablo Ramirez and Nicolas Masson
* @link https://webrider.de/
* @since 1.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace CakephpFixtureFactories\ORM;

use Cake\ORM\Query\QueryFactory;
use Cake\ORM\Query\SelectQuery;
use Cake\ORM\Table;
use Cake\TestSuite\TestCase;
use CakephpFixtureFactories\Factory\BaseFactory;
use PHPUnit\Framework\MockObject\MockObject;

/**
* A class to mock select queries result set for a given table
*/
class SelectQueryMocker
{
/**
* @param \Cake\TestSuite\TestCase $testCase test case should extend CakePHP's Test Case
* @param \CakephpFixtureFactories\Factory\BaseFactory $factory fixture factory which non persisted entities will be returned by the select query
* @param string|null $alias The model to get a mock for.
* @param array<string> $methods The list of methods to mock
* @param array<string, mixed> $options The config data for the mock's constructor.
* @throws \Cake\ORM\Exception\MissingTableClassException
* @return \Cake\ORM\Table|\PHPUnit\Framework\MockObject\MockObject
*/
public static function mock(
TestCase $testCase,
BaseFactory $factory,
?string $alias = null,
array $methods = [],
array $options = []
): Table|MockObject {
$alias = $alias ?? $factory->getTable()->getAlias();
$resultSet = $factory->getResultSet();
$selectQueryMocked = $testCase
->getMockBuilder(SelectQuery::class)
->setConstructorArgs([$factory->getTable()])
->onlyMethods(['count', 'all'])
->getMock();
$selectQueryMocked
->method('count')
->willReturn($resultSet->count());
$selectQueryMocked
->method('all')
->willReturn($resultSet);

$queryFactoryMocked = $testCase
->getMockBuilder(QueryFactory::class)
->onlyMethods(['select'])
->getMock();
$queryFactoryMocked
->method('select')
->willReturn($selectQueryMocked);

$options['queryFactory'] = $queryFactoryMocked;

return $testCase->getMockForModel($alias, $methods, $options);
}
}
Loading

0 comments on commit 446ec21

Please sign in to comment.