logo

Testing

Testing is at the heart of our development philosophy at InnoGE. Ensuring that our applications not only function as intended but also withstand unexpected scenarios is vital to delivering quality software.

Our Testing Philosophy

In a rapidly evolving digital world, we believe that robust testing is not just a phase in the development process but an integral component of it. A 100% test coverage in our backend sets the gold standard for our applications. It's not about chasing a number; it's about ensuring the reliability and robustness of our systems.

Levels of Testing

1. Feature Tests

These tests validate entire features. Given the comprehensive nature of feature tests, they tend to touch various components of the application, including databases, mailers, and sometimes even external services.

Due to the rich set of functionalities provided by Laravel, feature tests are our primary mode of testing. With feature tests, we can:

  • Ensure that our APIs return the expected responses
  • Verify that the right data gets stored in our databases
  • Mock emails and verify their content
  • And many more

2. Unit Tests

Unit tests are microscopic in nature. Their primary purpose is to validate that a particular unit of code (usually methods) behaves as expected under various circumstances.

For instance, a unit test can validate that:

  • A particular function returns the expected result for a given input
  • Exceptions are thrown when they should be
  • Mocked methods on objects get called the expected number of times

Tools & Frameworks

While tools and frameworks facilitate testing, the real essence lies in the test cases' logic. Here's how we leverage tools:

  • PestPHP: An elegant PHP testing framework. Its minimalistic and expressive syntax makes writing tests more enjoyable and readable.
  • Laravel Dusk: Laravel Dusk provides an expressive testing API and browser automation for our applications.

Mocking

Ensuring our code is testable often requires mocking parts of our system. By isolating components, we can ensure that we're only testing the piece of code in focus. Laravel's built-in mocking features are incredibly powerful and often preferred.

Continuous Testing

Our commitment to quality doesn't stop at writing tests. Using GitHub Actions, we ensure our test suites run automatically upon every code push. This ensures that our codebase remains stable and regressions are caught early.

Test Data & Factories

Effective testing often requires creating realistic test data. With Laravel factories and the Faker PHP library, generating data that mimics real-world scenarios becomes a breeze.

Test Design

The Arrange-Act-Assert (AAA) pattern is our cornerstone. This approach ensures clarity, as anyone reviewing the test can quickly discern its purpose and the logic it's testing.

UI/UX & Manual Testing

No test suite can replace the human eye and intuition. Automated tests ensure our code works; manual tests ensure it feels right to the end-user.

Test Reviews

All tests undergo rigorous review. Readability is key. A future developer (or even you, three months from now) should be able to understand why a test was written and what it's checking.

Tips for Readable Test Code

  • Descriptive Naming: 'testUserRegistration' is okay; 'it_registers_a_new_user_when_given_valid_data' is better.
  • Consistency: A consistent testing structure makes navigating test suites easier.
  • Utilize Comments Sparingly: While comments can clarify complex logic, over-reliance can make your tests cluttered. Let the test names and assertions speak for themselves whenever possible.

By adhering to these practices, our tests do double duty: they ensure our code works as expected, and they act as living documentation of our code's expected behavior.

Test examples

PestPHP, with its minimalistic and expressive syntax, allows for clean and clear testing of our Laravel applications. Here are some examples:

Feature Test

Let's test a basic feature where a user can be registered:

php
use App\Models\User;

it('registers a new user with valid data', function () {
    $userData = [
        'name' => 'John Doe',
        'email' => 'john@example.com',
        'password' => 'password123',
        'password_confirmation' => 'password123',
    ];

    post(route('register'), $userData)
        ->assertRedirect(route('dashboard'));

    $user = User::where('email', 'john@example.com')->first();
    
    expect($user)->toBeInstanceOf(User::class);
    expect($user->name)->toBe('John Doe');
});

Database Interaction:

Testing if a user is saved to the database:

php
use App\Models\User;

it('saves user to the database', function () {
    User::create([
        'name' => 'John',
        'email' => 'john@example.com',
        'password' => bcrypt('password123'),
    ]);
    
    $user = User::where('email', 'john@example.com')->first();
    
    expect($user)->toBeInstanceOf(User::class);
});

Unit Test

Suppose you have a simple utility function in your Laravel application to calculate the area of a rectangle:

php
// Utility function in some utility class
public function calculateArea($length, $breadth) {
    return $length * $breadth;
}

// PestPHP test
it('calculates area of rectangle correctly', function () {
    $util = new UtilityClass();
    
    $area = $util->calculateArea(5, 4);
    
    expect($area)->toEqual(20);
});

Mocking

Let's say you have a service that charges a user. You can mock this to avoid real charges during tests:

php
use App\Services\PaymentService;

it('charges the user', function () {
    $paymentService = $this->mock(PaymentService::class);
    $paymentService->shouldReceive('charge')->andReturn(true);
    
    $result = $paymentService->charge(100); // 100 cents or $1
    
    expect($result)->toBeTrue();
});