Skip to content

Commit

Permalink
Merge pull request #58 from barnabaskecskes/feature/useFactoriesWhenC…
Browse files Browse the repository at this point in the history
…reatingRelations

Using factories when creating relations
  • Loading branch information
christophrumpel committed Jul 13, 2020
2 parents bd4fdfc + 611fc38 commit c36ac43
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 10 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,14 @@ Here were are getting a user instance that has three related recipes attached. T

> :warning: **Note**: For this to work, you need to have a new RecipeFactory already created.
You can also define extras for the related models when using related model factories directly.

```php
$user = UserFactory::new()
->withFactory(RecipeFactory::new()->withCustomName(), 'recipes', 3)
->create();
```

You can create many related models instances by chaining `with`s.

```php
Expand Down
18 changes: 12 additions & 6 deletions src/BaseFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected function build(array $extra = [], string $creationType = 'create')
$modelData = $this->transformModelFields(
array_merge($this->getDefaults($this->faker), $this->overwriteDefaults, $extra)
);
$model = $this->unguardedIfNeeded(fn () => $this->modelClass::$creationType($modelData));
$model = $this->unguardedIfNeeded(fn() => $this->modelClass::$creationType($modelData));

if ($this->relatedModelFactories->isEmpty()) {
return $model;
Expand All @@ -51,7 +51,7 @@ protected function build(array $extra = [], string $creationType = 'create')

protected function unguardedIfNeeded(\Closure $closure)
{
if (! config('factories-reloaded.unguard_models')) {
if (!config('factories-reloaded.unguard_models')) {
return $closure();
}

Expand All @@ -67,13 +67,19 @@ public function times(int $times = 1): MultiFactoryCollection

/** @return static */
public function with(string $relatedModelClass, string $relationshipName, int $times = 1): self
{
return $this->withFactory($this->getFactoryFromClassName($relatedModelClass), $relationshipName, $times);
}

/** @return static */
public function withFactory(FactoryInterface $relatedFactory, string $relationshipName, int $times = 1): self
{
$clone = clone $this;

$clone->relatedModelFactories = clone $clone->relatedModelFactories;
$clone->relatedModelFactories[$relationshipName] ??= collect();
$clone->relatedModelFactories[$relationshipName] = $clone->relatedModelFactories[$relationshipName]->merge(
collect()->times($times, fn () => $this->getFactoryFromClassName($relatedModelClass))
collect()->times($times, fn() => $relatedFactory)
);

return $clone;
Expand All @@ -97,7 +103,7 @@ public function overwriteDefaults($attributes): self
protected function getFactoryFromClassName(string $className): FactoryInterface
{
$baseClassName = (new ReflectionClass($className))->getShortName();
$factoryClass = config('factories-reloaded.factories_namespace').'\\'.$baseClassName.'Factory';
$factoryClass = config('factories-reloaded.factories_namespace') . '\\' . $baseClassName . 'Factory';

return new $factoryClass($this->faker);
}
Expand All @@ -120,7 +126,7 @@ private function buildRelationsForModel(Model $model, string $creationType): Mod

if (method_exists($relation, 'associate')) {
$relatedModels = $factories->map->$creationType();
$relatedModels->each(fn ($related) => $relation->associate($related));
$relatedModels->each(fn($related) => $relation->associate($related));

if ($creationType === 'create') {
$model->save();
Expand All @@ -129,7 +135,7 @@ private function buildRelationsForModel(Model $model, string $creationType): Mod
continue;
}

throw new InvalidArgumentException('Unsupported relation `'.$relationshipName.'` of ` type `'.get_class($relation).'`.');
throw new InvalidArgumentException('Unsupported relation `' . $relationshipName . '` of ` type `' . get_class($relation) . '`.');
}

return $model;
Expand Down
93 changes: 89 additions & 4 deletions tests/FactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ public function it_lets_you_overwrite_default_data_through_factory_methods(): vo
public function it_lets_you_overwrite_default_data_when_creating_multiple_instances(): void
{
$pancakes = RecipeFactory::new()
->times(5)
->create(['name' => 'Pancakes']);
->times(5)
->create(['name' => 'Pancakes']);

$this->assertEquals('Pancakes', $pancakes->first()->name);
}
Expand Down Expand Up @@ -208,8 +208,8 @@ public function it_lets_you_use_a_closure_for_defining_default_data(): void
public function it_lets_you_use_a_closure_for_overriding_default_data(): void
{
$ingredient = IngredientFactoryUsingClosure::new()->create([
'name' => fn (array $ingredient) => 'Basil',
'description' => fn (array $ingredient) => "Super delicious {$ingredient['name']}",
'name' => fn(array $ingredient) => 'Basil',
'description' => fn(array $ingredient) => "Super delicious {$ingredient['name']}",
]);

$this->assertIsString($ingredient->name);
Expand Down Expand Up @@ -273,6 +273,91 @@ public function the_factory_is_immutable_when_adding_related_models(): void
$this->assertEquals(4, $secondGroup->recipes()->count());
}

/** @test * */
public function it_lets_you_add_a_related_model_using_a_factory(): void
{
Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories');

$group = GroupFactory::new()
->withFactory(RecipeFactory::new(), 'recipes')
->create();

$this->assertEquals(1, $group->recipes->count());
$this->assertInstanceOf(Recipe::class, $group->recipes->first());
}

/** @test * */
public function it_lets_you_add_a_related_model_using_a_factory_with_make(): void
{
Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories');

$group = GroupFactory::new()
->withFactory(RecipeFactory::new(), 'recipes')
->make();

$this->assertEquals(1, $group->recipes->count());
$this->assertEquals(0, Recipe::count());
$this->assertEquals(0, Group::count());
}

/** @test * */
public function it_lets_you_add_multiple_related_models_using_factories(): void
{
Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories');

$group = GroupFactory::new()
->withFactory(RecipeFactory::new(), 'recipes', 4)
->create();

$this->assertEquals(4, $group->recipes->count());
$this->assertInstanceOf(Recipe::class, $group->recipes->first());
}

/** @test * */
public function the_factory_is_immutable_when_adding_related_models_using_factories(): void
{
Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories');

$group = GroupFactory::new()
->withFactory(RecipeFactory::new(), 'recipes', 4);

$firstGroup = $group->withFactory(RecipeFactory::new(), 'recipes')->create();
$secondGroup = $group->create();

$this->assertEquals(5, $firstGroup->recipes()->count());
$this->assertEquals(4, $secondGroup->recipes()->count());
}

/** @test */
public function it_lets_you_define_extras_for_related_models_using_factories()
{
Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories');

$group = GroupFactory::new()
->withFactory(RecipeFactory::new()
->withCustomName()
->withCustomDescription(), 'recipes')
->create();

tap($group->recipes()->first(), function ($recipe) {
$this->assertEquals('my-name', $recipe->name);
$this->assertEquals('my-desc', $recipe->description);
});
}

/** @test */
public function it_lets_you_define_extras_for_multiple_related_models_at_once_using_factories()
{
Config::set('factories-reloaded.factories_namespace', 'ExampleAppTests\Factories');

$group = GroupFactory::new()
->withFactory(RecipeFactory::new()->withCustomName(), 'recipes', 3)
->create();

$this->assertEquals(3, $group->recipes()->count());
$group->recipes->each(fn($recipe) => $this->assertEquals('my-name', $recipe->name));
}

/** @test */
public function it_works_with_factory_as_relationship(): void
{
Expand Down

0 comments on commit c36ac43

Please sign in to comment.