Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#235 First draft of v3.1 #237

Open
wants to merge 1 commit into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading