Factories with relationship fields are calling create() when calling make() #36169
Replies: 4 comments 3 replies
-
@taylorotwell sorry, but in my opinion, this behavior is an error. Because as the doc says “the make method to create models without persisting them to the database”. Therefore, you should not use the database at any time. I had to install an SQLite database in memory and use the migration to be able to use the factories in my unit tests. However, the performance of the tests was very poor. Before, tests took about 2 minutes with xdebug and coverage enabled. Now, without xdebug and coverage, it takes 16 minutes. Very bad 😫 |
Beta Was this translation helpful? Give feedback.
-
Exactly the same situation than @robertoarruda . |
Beta Was this translation helpful? Give feedback.
-
@adri3nam To get around this problem (which still has no solution), I made this horrible code on my system. <?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\Factory as BaseFactory;
use Illuminate\Database\Eloquent\Model;
abstract class Factory extends BaseFactory
{
/**
* @var bool
*/
private bool $isCreating = false;
/**
* @param array $attributes
* @param Model|null $parent
* @return Collection|Model|mixed
*/
public function create($attributes = [], ?Model $parent = null)
{
$this->isCreating = true;
return parent::create($attributes, $parent);
}
/**
* @param array $definition
* @return array
*/
protected function expandAttributes(array $definition)
{
return collect($definition)->map(function ($attribute, $key) use (&$definition) {
if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) {
$attribute = $attribute($definition);
}
if ($attribute instanceof self) {
$attribute = ($this->isCreating) ? $attribute->create()->getKey() : 0;
} elseif ($attribute instanceof Model) {
$attribute = $attribute->getKey();
}
$definition[$key] = $attribute;
return $attribute;
})->all();
}
} |
Beta Was this translation helpful? Give feedback.
-
As the Laravel core is not willing to change the behaviour, an alternative without changing the A trait similar to this: use Illuminate\Database\Eloquent\Factories\Factory;
trait WithoutRelationships
{
public function withoutRelationships(): static
{
return $this->state(function () {
return collect($this->definition())
->filter(function ($value) {
return $value instanceof Factory;
})
->mapWithKeys(function ($item, $key) {
return [$key => null];
})
->toArray();
});
}
} Include this trait in your model Factory: class SomeFactory extends Factory
{
use WithoutRelationships;
...
} And then you could use this code in your tests: SomeModel::factory()->withoutRelationships()->make(); This would allow you to not care if you add another Model::factory() as a property later on. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Description:
I use factories in my unit tests. My tests never touch the database, so I don't use connections or migrations on it.
In my unit tests, factories use the make method only to provide fake data. However, even using the make method, apparently the related factories are using the create method.
My code has several relationships in the factories, but below is a simple example of the problem.
Steps To Reproduce:
PositionFactory:
FirmFactory:
The error presented is when running the tests, it is the attempt to create a user. I imagine it’s because most relationships are tied to that table
Debugging the code to solve the problem, I found the place where the create method is called.
Illuminate/Database/Eloquent/Factories/Factory.php
I believe this code should check first, whether it is called by the make or create method, and then use the correct method.
(If I switch to the make method, the tests work).
Beta Was this translation helpful? Give feedback.
All reactions