Laravel 8 factory states

Laravel 8 was released earlier this month. The model factories are now class based and that has some advantages.

I am working on a project with questionnaires. For a typical test scenario, I would need a questionnaire of a certain type with at least one question and an associated user attempt.
This is how I would write it before:


$questionnaire = Questionnaire::factory()->create(['type' => Questionnaire::TYPE_POLL);
Question::factory()->create(['questionnaire_id' => $questionnaire->id]);
Attempt::factory()->create([
        'questionnaire_id' => $questionnaire->id,
        'user_id' => $user->id,
        'status' => Attempt::STATUS_FINISHED,
]);

and now:


$questionnaire = Questionnaire::factory()
                            ->poll()
                            ->withQuestions(1)
                            ->withAttempt($user, true)
                            ->create();

That looks awesome! :) A big improvement for readability and it will speed up writing your tests.
This is the QuestionnaireFactory model factory:


namespace Database\Factories;

use App\Models\Attempt;
use App\Models\Question;
use App\Models\Questionnaire;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

class QuestionnaireFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var  string
     */
    protected $model = Questionnaire::class;

    /**
     * Define the model's default state.
     *
     * @return  array
     */
    public function definition()
    {
        return [
            'title' => $this->faker->sentence,
            'description' => $this->faker->sentences(2, true),
            'feedback_type' => Questionnaire::FEEDBACK_AFTER_BOTH,
            'scoring_method' => Questionnaire::SCORING_BEST,
            'allowed_attempts' => 1,
            'max_time' => 0,
            'public' => true,
            'active' => true,
            'questions_random_order' => true,
            'type' => Questionnaire::TYPE_TEST,
            'status' => Questionnaire::STATUS_VALID,
            'slug' => Str::random(),
        ];
    }

    /**
     * Indicate that the questionnaire is a poll
     *
     * @return  \Illuminate\Database\Eloquent\Factories\Factory
     */
    public function poll()
    {
        return $this->state([
            'type' => Questionnaire::TYPE_POLL,
        ]);
    }

    /**
     * Indicate that the questionnaire is a survey
     *
     * @return  \Illuminate\Database\Eloquent\Factories\Factory
     */
    public function survey()
    {
        return $this->state([
            'type' => Questionnaire::TYPE_SURVEY,
        ]);
    }

    /**
     * Adds questions to the questionnaire
     *
     * @param  int $questions
     * @return  QuestionnaireFactory
     */
    public function withQuestions(int $questions = 1)
    {
        return $this->afterCreating(function (Questionnaire $questionnaire) use ($questions) {
            Question::factory()->count($questions)->create([
                'questionnaire_id' => $questionnaire->id,
                'type' => Question::TYPE_RATING,
            ]);
        });
    }

    /**
     * Add a user attempt to the questionnaire
     *
     * @param  User $user
     * @param  bool $finished
     * @return  QuestionnaireFactory
     */
    public function withAttempt(User $user, bool $finished = false)
    {
        return $this->afterCreating(function (Questionnaire $questionnaire) use($user, $finished) {
            Attempt::factory()->create([
                'questionnaire_id' => $questionnaire->id,
                'user_id' => $user->id,
                'status' => $finished? Attempt::STATUS_FINISHED:Attempt::STATUS_INCOMPLETE,
            ]);
        });
    }
}