# Tests

General rule about test classes and methods is focus on what really matters in a test case. As we always try to write minimum code to have the requirements met, we should also try to write minimum test code. Doing this makes it easy to understand what's really happening in a specific test case.

# Types

In general application code test can be split into 2 different types: feature and unit

# Feature tests

Feature tests are generally need application instance, container, usually interacts with HTTP and database. These tests considered as high-level and they test different parts of the application as one piece.

Tests that cover HTTP, console command, model relations, etc considered as feature test.

All feature tests should extend base Tests\TestCase class since this class boots up Laravel instance with each test.

# Unit tests

Unit tests usually are the one can be run without needing database connection, application instance, etc. They test a single unit and can be tested isolation, without needing any other dependant code parts.

Single class or method tests that not using any outside factor considered as unit test.

All unit test classes should extend PHPUnit\Framework\TestCase class.

# Naming

Each test class should follow the folder structure in app directory, only prefixed by being either feature or unit test. Test class name should also match with class it is testing with Test suffix

For example, if you are testing App\Http\Controllers\HomeController with a feature test, then test class should be Tests\Feature\App\Http\Controllers\HomeControllerTest.

Likewise, if you are testing App\Services\VatCalculator with a unit test, then class should be Tests\Unit\App\Services\VatCalculatorTest.

This makes it easy for to scan and match classes with their test classes.

# Methods

All tests methods should in camelCase and must be return typed with void.

Tests methods should always be prefixed with test. Avoid using docblocks to mark a method as test case.

// Bad
/** @test */
public function createUserWithGivenAttributes(): void
{
    //
}

// Good
public function testCreateUserWithGivenAttributes(): void
{
    //
}

Test methods should test against one thing. If you are asserting too many things in a single test case you need to extract every separate asserting groups into their own test methods.

// Bad
public function testCreateUserWithGivenAttributes(): void
{
    // prepare attributes
    
    // create user
    
    // check attributes with created user's attributes
    // assert that UserCreated event is dispatched
}
// Good
public function testCreateUserWithGivenAttributes(): void
{
    // prepare attributes
    
    // create user
    
    // check attributes with created user's attributes
    // assert that UserCreated event is dispatched
}

public function testCreatingUserDispatchesUserCreatedEvent(): void
{
    // create user

    // assert that UserCreated event is dispatched
}

When writing test cases, try to be make method names short but expressive. General rule is, when reading test method name, it should be clear what that test method is testing. If method name is too long and multiple assertions, this is good indicator that you might need to extract that test case into multiple small test cases.

// Bad: Too short and not expressive enough
public function testUser(): void
{
    //
}

// Bad: Too long and asserting multiple things
public function testCreateUserWithGivenAttributesReturnsUserModelAndDispatchUserCreatedEvent(): void
{
    //
}

// Good: expressive and focuses only on one thing
public function testCreateUserWithGivenAttributes(): void
{
    //
}

# Happy path

In your test classes happy path should always come as first test method. This makes it visually easy to scan and understand what test class does immediately after you open it.

After the happy path you should cover all the unhappy path test cases. Things like: authentication, authorization, exceptions, validation errors, application aborts, etc.

# setUp() and tearDown()

For the same above reason, you should always put your setUp() and tearDown() methods at the end of the test class.

# Using test classes

If you need a specific class for your test cases, you should keep them within the same test file when possible. When you want to reuse test classes throughout tests, it's fine to make a dedicated class instead. Here's an example of internal classes:

namespace Tests\Feature\Conterns\GeneratesSlug;

use App\Models\Concerns\GeneratesSlug;
use Tests\TestCase;

class GeneratesSlugTest extends TestCase
{
    public function testGeneratesSlugWhenCreatingNewModel(): void
    {
        $model = new TestModel();
        //
    }
}

class TestModel extends \Illuminate\Database\Eloquent\Model
{
    use GeneratesSlug;
}