Summer Sale! All accounts are 50% off this week.

vincent15000's avatar

What is the best pratice for writing tests ?

Hello,

I begin my first tests and I wonder if I do that correctly. To be more precise, I never did tests, so I discover both tests and tests with Livewire / PHP Unit / Features.

I have read the documentation and some MOOC about the subject, but I'm not sure about what I have done.

Here is an example of what I have done for testing C(R)UD.

In my code, you can see that I create a new group (test for creating), then I update the group that has just been created in the previous test (test for updating) and finally I delete the updated group (test for deleting).

/** @test  */
public function admin_can_create()
{
    $user = User::where('admin', true)->first();
    $this->actingAs($user);
    Livewire::test(AdminGroupForm::class)
        ->set('updateMode', false)
        ->set('group.name', 'Nouveau groupe')
        ->set('group.description', 'Ceci est un nouveau groupe')
        ->call('save');
    $result = Group::
        where('name', 'Nouveau groupe')
        ->where('description', 'Ceci est un nouveau groupe')
        ->exists();
    $this->assertTrue($result);
}

/** @test  */
public function admin_can_update()
{
    $user = User::where('admin', true)->first();
    $this->actingAs($user);
    $group = Group::
        where('name', 'Nouveau groupe')
        ->where('description', 'Ceci est un nouveau groupe')
        ->first();
    Livewire::test(AdminGroupForm::class)
        ->set('updateMode', true)
        ->call('initForm', $group->id)
        ->set('group.name', 'Nouveau groupe corrigé')
        ->set('group.description', 'Ceci est une nouvelle description corrigée')
        ->call('save');
    $result = Group::
        where('name', 'Nouveau groupe corrigé')
        ->where('description', 'Ceci est une nouvelle description corrigée')
        ->exists();
    $this->assertTrue($result);
}

/** @test  */
public function admin_can_delete()
{
    $user = User::where('admin', true)->first();
    $this->actingAs($user);
    $group = Group::
        where('name', 'Nouveau groupe corrigé')
        ->where('description', 'Ceci est une nouvelle description corrigée')
        ->first();
    Livewire::test(AdminGroups::class)
        ->call('delete', $group->id);
    $result = Group::
        where('name', 'Nouveau groupe corrigé')
        ->where('description', 'Ceci est une nouvelle description corrigée')
        ->doesntExist();
    $this->assertTrue($result);
}

Perhaps it would be better, for my update and delete test to create a group (inside the test method) and work with this created group ? Because if the create test does not work, this means that automatically my update and delete tests won't work too.

And what about using factories to generate the contents for testing ? And where use them ? Inside the test methods ? Manually before the test methods ?

I think I'm a little lost.

Could you help me ? ;)

Thanks a lot.

Vincent

0 likes
12 replies
Nakov's avatar

First of all this is the worse practice

$user = User::where('admin', true)->first();

never seen anyone doing it that way.

I already answered in your last thread about using factories, and if you say that you read the documentation you should've been aware of them too. There are tons of video tutorials practicing TDD in Laravel, why don't you watch some. Here is one that recently was published and it covers both PHPUnit and Pest https://spatie.be/videos/testing-laravel-with-pest/test-driven-development

2 likes
vincent15000's avatar

@Nakov ok thank you ... that's true I have not seen any tutorial for Laravel ... only small MOOC about the theory

vincent15000's avatar

@Nakov Yes you suggested me to use factories, but I didn't understand where ... I will have a look at some tutorials using Laravel.

Sinnbeck's avatar

Tests should never be dependent on each other. You should be able to run them in random order and still have all green

1 like
vincent15000's avatar

@Sinnbeck Ok ... effectively my tests are not independant ... so that means ... if I want to test deleting a row, it's necessary to create this row inside the test ? Or is it a good idea to create a lot of data with factories and work with them ?

Sinnbeck's avatar

@vincent15000 you can have a tearDown() method that cleans up after each test. And setUp() to prepare stuff. They are automatically called by phpunit

1 like
vincent15000's avatar

@Sinnbeck and @nakov Here is an example of what I have done according to your suggestions.

My tests are now independant but I'm not sure if it's a good idea how I test the update of an author. It works ... but ...

Same question for create test, perhaps it would be a better idea to use a factory with make() to create my model, then pass it to the Livewire component to be saved and then test if the model exists.

UPDATED WITHOUT THE PRIVATE PROPERTIES

class AdminTypeTest extends TestCase
{

    private User $user;

    protected function setUp(): void
    {
        parent::setUp();
        $this->user = User::factory()->admin()->create();
    }

    protected function tearDown(): void
    {
        parent::tearDown();
        $this->user->delete();
    }

    /** @test  */
    public function admin_can_access_table()
    {
        $response = $this->actingAs($this->user)->get('admin/types');
        $response->assertStatus(200);
    }

    /** @test  */
    public function admin_can_create()
    {
        $this->actingAs($this->user);
        $type = Type::factory()->make();
        Livewire::test(AdminTypeForm::class)
            ->set('updateMode', false)
            ->set('type.name', $type->name)
            ->set('type.description', $type->description)
            ->call('save');
        $type = Type::
            where('name', $type->name)
            ->where('description', $type->description)
            ->first();
        $this->assertModelExists($type);
        $type->delete();
    }

    /** @test  */
    public function admin_can_update()
    {
        $this->actingAs($this->user);
        $type = Type::factory()->create();
        $typeUpdated = Type::factory()->make();
        Livewire::test(AdminTypeForm::class)
            ->set('updateMode', true)
            ->call('initForm', $type->id)
            ->set('type.name', $typeUpdated->name)
            ->set('type.description', $typeUpdated->description)
            ->call('save');
        $doesntExist = Type::
            where('name', $type->name)
            ->where('description', $type->description)
            ->doesntExist();
        $exists = Type::
            where('name', $typeUpdated->name)
            ->where('description', $typeUpdated->description)
            ->exists();
        $this->assertTrue($doesntExist);
        $this->assertTrue($exists);
        $type->delete();
    }

    /** @test  */
    public function admin_can_delete()
    {
        $this->actingAs($this->user);
        $type = Type::factory()->create();
        Livewire::test(AdminTypes::class)
            ->call('delete', $type->id);
        $this->assertDeleted($type);
    }

    /** @test  */
    public function member_cannot_access_table()
    {
        $user = User::factory()->create();
        $response = $this->actingAs($user)->get('admin/types');
        $response->assertStatus(302);
        $user->delete();
    }

}
Nakov's avatar
Nakov
Best Answer
Level 73

@vincent15000 Much better, you see you can do it.

As for your question, if you are creating a new user a proper test would be to fill in the fields in the livewire form without a factory but just with data from your test and run an expectation/assertion if a record with that data exists in the database afterwards.

And if you want test data, yes, use ->make() rather than create() because create also creates the record in the database directly, skipping your livewire component.

vincent15000's avatar

@Nakov Ok ... so what you mean is to simulate to fill the fields in the livewire form ... for the moment I just set the variables via simple set() methods. I'll have a look how to simulate to fill fields in a form.

That can be done with dusk isn't it ? It looks great.

Nakov's avatar

@vincent15000 no, I meant using set as this is not a browser test, but you don't need a factory in order to set the variables, was my point. But yeah, you could use Dusk if you want to run browser tests.

vincent15000's avatar

@Nakov Ok thank you ;) ... I have changed some lines in my code in order to use make() to generate the datas via the factory, it's more easy to manage and then I pass them to the livewire component using set(), so I can test my livewire controller methods.

I try now the dusk package to test the form.

Please or to participate in this conversation.