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.
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.
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:
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:
While tools and frameworks facilitate testing, the real essence lies in the test cases' logic. Here's how we leverage tools:
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.
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.
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.
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.
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.
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.
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.
PestPHP, with its minimalistic and expressive syntax, allows for clean and clear testing of our Laravel applications. Here are some examples:
Let's test a basic feature where a user can be registered:
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');
});Testing if a user is saved to the database:
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);
});Suppose you have a simple utility function in your Laravel application to calculate the area of a rectangle:
// 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);
});Let's say you have a service that charges a user. You can mock this to avoid real charges during tests:
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();
});