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

lara372116's avatar

Testing convention

Hi everyone i am starting with testing and i watched Jeffrey course for testing and now i am watching pest Laravel driven and i read the docs about testing and i still do not know where do i put tests business logic, where tests for models and etc.. What is your practise?

0 likes
5 replies
LaryAI's avatar
Level 58

When organizing your tests in a Laravel application, it's important to follow a structure that makes it easy to maintain and understand. Here's a common convention for organizing your tests:

  1. Directory Structure:

    • Place all your tests in the tests directory at the root of your Laravel project. Laravel typically has two main directories within tests: Feature and Unit.
  2. Unit Tests:

    • These tests are meant for testing individual units of code, such as methods in a class. They are usually placed in the tests/Unit directory.
    • For example, if you have a model with some business logic, you might create a test file like tests/Unit/YourModelTest.php.
  3. Feature Tests:

    • These tests are used to test larger portions of your application, such as HTTP requests, database interactions, and more. They are placed in the tests/Feature directory.
    • For example, if you are testing a controller or a route, you might create a test file like tests/Feature/YourFeatureTest.php.
  4. Testing Business Logic:

    • If your business logic is encapsulated within models or service classes, you can write unit tests for these.
    • If the business logic involves multiple components interacting (e.g., a controller action that uses a model and a service), you might write a feature test to cover the entire flow.
  5. Naming Conventions:

    • Name your test classes and methods clearly to reflect what they are testing. For example, UserCanRegisterTest for a feature test that checks user registration.
  6. Example: Here's a simple example of how you might structure a unit test for a model:

    // tests/Unit/YourModelTest.php
    
    namespace Tests\Unit;
    
    use Tests\TestCase;
    use App\Models\YourModel;
    use Illuminate\Foundation\Testing\RefreshDatabase;
    
    class YourModelTest extends TestCase
    {
        use RefreshDatabase;
    
        /** @test */
        public function it_can_perform_some_business_logic()
        {
            $model = new YourModel();
            $result = $model->someBusinessLogic();
    
            $this->assertTrue($result);
        }
    }
    

    And a feature test example:

    // tests/Feature/YourFeatureTest.php
    
    namespace Tests\Feature;
    
    use Tests\TestCase;
    use Illuminate\Foundation\Testing\RefreshDatabase;
    
    class YourFeatureTest extends TestCase
    {
        use RefreshDatabase;
    
        /** @test */
        public function it_can_access_a_specific_route()
        {
            $response = $this->get('/your-route');
    
            $response->assertStatus(200);
        }
    }
    

By following these conventions, you can keep your tests organized and make it easier for you and your team to understand and maintain them.

martinbean's avatar

@mileprogramer I personally just create feature tests (in PHPUnit rather than Pest). Feature tests by nature then add a lot of test coverage to your application.

So, to test a controller that creates an article:

public function test_user_can_create_article(): void
{
    $user = User::factory()->create();

    // Assert start off with no rows in 'articles' table
    $this->assertDatabaseEmpty('articles');

    $this
        ->actingAs($user)
        ->post('/articles', [
            'title' => 'Test Article',
            'excerpt' => 'This is a test article.',
            'content' => 'This is the test article body.',
            'is_published' => '1',
        ])
        ->assertValid() // Assert no validation errors occurred
        ->assertRedirect('/articles'); // Assert redirected back to article index

    // Assert row was inserted into database table
    $this->assertDatabaseHas('articles', [
        'author_id' => $user->getKey(),
        'title' => 'Test Article',
        'excerpt' => 'This is a test article.',
        'content' => 'This is the test article body.',
        'is_published' => true,
    ]);
}

This will test the controller, its validation, that the correct fields in the request are mapped to the correct columns in the database, etc.

You can then add additional test cases to test other use cases, such as the correct validation rules being in place:

#[TestWith(['title'])]
#[TestWith(['excerpt'])]
#[TestWith(['content'])]
public function test_field_is_required(string $field): void
{
    $data = [
        'title' => 'Test Article',
        'excerpt' => 'This is a test article.',
        'content' => 'This is the test article body.',
        'is_published' => '1',
    ];

    // Remove field from data, to trigger 'required' validation failure
    $data = Arr::forget($data, $field);

    $this
        ->actingAs($user)
        ->post('/articles', $data)
        ->assertInvalid($field); // Assert validation fails for field
}
lara372116's avatar

Thanks man, i tried what lary told me to test mine model logic in the unit test but i am getting the error Call to a member function connection() on null and i just read this some conversation that unit test should be independent so where do i place now mine testing logic for the model

martinbean's avatar

@mileprogramer You shouldn’t be trying to access a database in a unit test. Unit tests are for testing a single unit of code, i.e. an individual method, and should not require anything “outside” in order to run that test. If your test needs to boot the framework, access the database, etc then it’s not a unit test.

lara372116's avatar

@martinbean Thanks man, but where then do i test mine model(scope and other logic for model) where i should put that

Please or to participate in this conversation.