Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

NBennett's avatar

Factories & Testing with multiple objects

I'm finding it difficult to write tests when they involve instantiating multiple levels of relational factories.

I have the following test: a_user_can_access_weekly_statistics

This test involves getting quiz data for various days of the week, such as the percentage of correct answers for a particular quiz on a particular day. There is 1 quiz per day, and multiple questions per quiz, and multiple answers per question. Then, there are multiple user answers per question. I wonder how, with this highly relational data, how I would go about structuring factories?

Specifically, best practices - I find that if my test contains a factory, which is then used as a ref in more factories, which are then used in more factories, that it is anything but robust and ideal.

Any advice would be super helpful.

0 likes
4 replies
AlbertLabarento's avatar

I was able to manage to work on my own entity factory using eloquent models, and hopefully someday it will also support doctrine. Here is the code.

<?php
declare(strict_types=1);

namespace Tests\Tools\EntityFactories\Interfaces;

use Illuminate\Database\Eloquent\Model;

interface EntityFactoryGeneratorInterface
{
    /**
     * Persist a model object.
     *
     * @param string $entityClass
     * @param null|mixed[] $data
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function persistEntity(string $entityClass, ?array $data = null): Model;

    /**
     * Save model object to database.
     *
     * @return void
     */
    public function resolve(): void;
}
<?php
declare(strict_types=1);

namespace Tests\Tools\EntityFactories\Interfaces;

use Illuminate\Database\Eloquent\Model;

interface EntityFactoryInterface
{
    /**
     * Create entity model.
     *
     * @param mixed[] $data
     *
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function create(array $data): Model;

    /**
     * Get default data for the model.
     *
     * @return mixed[]
     */
    public function getDefaultData(): array;
}

Example Entity

<?php
declare(strict_types=1);

namespace Tests\Tools\EntityFactories;

use App\Models\User;
use Illuminate\Database\Eloquent\Model;

class UserEntityGenerator extends AbstractEntityGenerator
{
    public function create(array $data): Model
    {
        return new User($data);
    }

    public function getDefaultData(): array
    {
        return [
            'first_name' => 'Albert'
        ];
    }
}

Usage

// We use database transaction so after each tests, data will be deleted from the database.
$this->entityFactory = new EntityGeneratorFactory($this->app->make(DatabaseTransactionInterface::class));

// We persist the data in the memory first. Useful for unit tests without saving it in the database.
$this->entityFactory->persistEntity(User::class, ['first_name' => 'David']);


// Then we decide to save it in the database, useful for functional testing.
$this->entityFactory->resolve();

Please or to participate in this conversation.