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

Charrua's avatar

New to phpUnit tests, need help with repeating factories

I'm new to writing tests and I have started with an application that's already running and I have a few questions:

I have my model Member with member data stored, that member has many Transaction and also many Testing (those are exams).

When writing unit tests for the member, on all tests I have to create the member, I see me repeating all the time some code.. Is this approach correct? Because when testing other parts maybe I have to create a member also... Is there a place to put code and reuse it?

Maybe instead of creating and repeating the member I can use a method inside testing methods that return always the same member. Is this a good idea or practice?

Do you use comments inside your unit testing methods or on the method header?

My unit test for a specific feature is looking like this but I see that's no so reading friendly. I wanted to simplify this.

/**
     * Member has pre paid testing corresponing with testing new rank?
     *
     * @return void
     */
    public function test_has_pre_paid_testing_corresponding_with_new_testing_rank()
    {
        $member = factory(Member::class)->create(['id' => '1']);
        $transaction = factory(Transaction::class)->create(['reason_id' => 10, 'member_id' => $member->id, 'school_id' => $member->school_id]); 
        $testing = factory(Testing::class)->create(['id' => '1', 'school_id' => $member->school_id]);        
        $testing->members()->attach($member->id);

        $this->assertEquals(1, $member->id);
        $this->assertEquals(10, $member->transactions()->first()->reason_id);
        $this->assertEquals($member->school_id, $testing->school_id);
        $this->assertEquals($member->id, $testing->members()->first()->id);
    }
0 likes
4 replies
drewdan's avatar

You can put it on the setup method

protected $member;

public function setUp(): void {
parent::setUp();
$this->member = factory(Member::class)->create();
}

the setup method is run before every test, so you can access the member using $this->member in your tests

1 like
bugsysha's avatar

When writing unit tests for the member, on all tests I have to create the member, I see me repeating all the time some code.. Is this approach correct? Because when testing other parts maybe I have to create a member also... Is there a place to put code and reuse it?

It all depends on your preferences. I love those explicit tests because they are the best examples of how something works and what are the dependencies for it. So in my mind, that is the "correct approach".

Maybe instead of creating and repeating the member I can use a method inside testing methods that return always the same member. Is this a good idea or practice?

I avoid any hardcoding cause that can reveal new issues that your system has. In my opinion, it is bad to do what you are doing by forcing the ID of the member when creating. When writing tests, after a while, you will see that there are some cases when singular tests pass, but as soon as you run the whole suite some will fail.

Do you use comments inside your unit testing methods or on the method header?

None. I avoid them. The code needs to be explicit/declarative enough to remove any need for comments.

My unit test for a specific feature is looking like this but I see that's no so reading friendly. I wanted to simplify this.

Ask yourself can Member exist in the perfect version of your system without the Transaction. If the answer is yes then your Member factory does not need to be modified.

Ask the same question for Transaction. If the answer is no, then you should add factory callbacks https://laravel.com/docs/7.x/database-testing#factory-callbacks

2 likes
Charrua's avatar

Great answer! You helped me so much!

I have still a question, on the last part, when using factory-callbacks:

A Member can exist on the system without a Transaction but the inverse can't.

So my logic is telling me that I need to create first the member and then the transaction, where should I place the factory-callback? On the member factory?

$factory->afterCreating(App\Member::class, function ($user, $faker) {
    $user->transactions()->save(factory(App\Transaction::class)->make());
});

This sounds good for you?

bugsysha's avatar

The callback should live on Transaction factory cause it is always called. But let's go a step back cause I've said Transaction when I've meant Testing.

Transaction has a member_id on it so that will be created automatically when you call factory(Transaction::class)->create().

But $testing->members()->attach($member->id); needs to be done at some point. So is that on the creation of Testing or at some other point? If it is on creation then you can use factory callbacks.

1 like

Please or to participate in this conversation.