Make sure you test your unit and develop higher-quality Laravel applications - (r)

Jul 3, 2024

-sidebar-toc>

Testing unit functionality is essential in developing software. It ensures that the software's elements function as intended in isolation. When you write tests specifically for unit of code, you are able to identify and fix errors early during development, resulting in better-performing and more stable software.

In a continuous integration/continuous delivery (CI/CD) pipeline, you can run these tests automatically after making codebase changes. It ensures that the new code doesn't introduce bugs or disrupt existing functions.

An introduction to PHPUnit

Laravel is compatible with testing through PHPUnit and includes a range of convenient helper methods that let you check your application.

Installing PHPUnit inside the Laravel project involves minimal configuration. Laravel offers a pre-configured testing environment that includes the phpunit.xml file and the dedicated testdirectory for your test files.

You can also change your phpunit.xml file to provide custom settings for the most customized test experience. You can also make an .env.testingenvironment file in the root directory of your project instead of making use of an .env file.

Test layout default in Laravel

It is the phpunit.xml file in the Laravel project is crucial to orchestrating the testing process, which ensures consistency throughout test runs, and permitting you to modify PHPUnit's behavior to suit your specific the requirements of your project. You are able to define how you want to conduct tests, which includes creating test suites, specifying the test environment, and creating database connections.

This file also specifies that the cache, session, and email should be set to the array driver, which will ensure no data session, cache or email records are kept when running tests.

It is possible to perform a range of testing on your Laravel application.

  • Testing features validates the broader capabilities of your app. The tests mimic HTTP requests and response and allow you to test routes as well as controllers and the interconnection of different elements. These tests make sure that the various components of your app work exactly as you would expect.
  • Testing of browsers --goes further by automating the browser's interactions. The tests use Laravel Dusk, the browser automation and test software, which simulates the user's interactions like the filling in of forms or clicking buttons. Browser tests are crucial for validating your application's behavior as well as user experience on real browsers.

Test-driven development concepts

Test-driven development (TDD) refers to a type of software development approach emphasizing testing before implementing the code. The approach is based on a procedure that is known as the red-green-refactor-cycle.

The test-driven development showing red-green-refactor.
The test-driven development cycle showing the red-green-refactor.

Here is an explanation of this cycle:

  • Red phase -- Write an entirely new test that defines the functionality of or improve an existing one before implementing the code. The test should fail (as "red" signifies) as there isn't a matching code to make it pass.
  • Phase Green Create just enough code to let the test fail, turning it from green to red. It may not be perfect but it will meet the specifications of the test case.
  • Refactoring Phase -- Refactor the code to improve its readability, maintainability, and performance while not altering the way it behaves. This is the point where you can comfortably make changes to your code and not worry about regression-related issues as the existing test cases detect them.

TDD has several benefits:

  • Early detection of bugs Early bug detection TDD helps catch bugs early during the process of development, helping reduce the cost and time required to fix problems late in the process of development.
  • Enhanced design -- TDD allows modular and loosely coupled code for better software design. It encourages you to contemplate the interface and interaction between components prior to implementing.
  • Confidence in refactoring It is possible to be confident in refactoring code as you know that tests already in place identify any regressions introduced during the process of refactoring.
  • Live documentation Test cases serve as living documentation by providing examples of how code ought to behave. The documentation will always be updated since failed tests indicate issues in the code.

In Laravel the development process, it is possible to use TDD principles by writing tests for the components such as controllers, models and services prior to implementing the components.

The testing environment of Laravel, which includes PHPUnit, provides convenient methods and assertions for TDD which allows you to develop meaningful tests, and adhere to the red-green-refactor cycle efficiently.

Simple examples of unit tests

This article outlines how you can create a test that will check your model's functionality.

The prerequisites

To follow along, you need the following:

  • A Laravel application. This tutorial is based on the application developed in the tutorial linked above. The guide can be read and create the blog application, but if you only require the source code needed to test the application, you can go through the steps listed below.
  • Xdebug installed and configured with coverage mode enabled.

Start the project

  1. Run this command from the terminal window in order to copy the project.
git clone https://github.com/VirtuaCreative/-laravel-blog.git
  1. Move into the project folder and execute the composer install command to install the project's dependencies.
  2. Rename your env.example file to .env.
  3. Run the php artist key:generate command to create an app key.

Create and run tests

In the beginning, you must have the appropriate project codes installed on your computer. The model you'll be testing is the Post model defined in the app/Http/Models/Post.php file. It includes several elements that are fillable, like title, description, and images.

It is your job to create simple unit tests that test the model. The first test ensures that the attributes are set correctly as well as a second test that examines mass assignment by attempting to assign an unfillable attribute.

  1. Execute the php artisan make:test PostModelFunctionalityTest --unit command to create a new test case. The option --unit option specifies that this is a unit-test and places it into the tests/Unitdirectory.
  2. Unlock the     tests/Unit/PostModelFunctionalityTest.php    File and replace it with the     test_example    This code will perform the following function:
public function test_attributes_are_set_correctly()
 
 // create a new post instance with attributes
 $post = new Post([
 'title' => 'Sample Post Title',
 'description' => 'Sample Post Description',
 'image' => 'sample_image.jpg',
 ]);
 
 // check if you set the attributes correctly
 $this->assertEquals('Sample Post Title', $post->title);
 $this->assertEquals('Sample Post Description', $post->description);
 $this->assertEquals('sample_image.jpg', $post->image);
 
 
 public function test_non_fillable_attributes_are_not_set()
 
 // Attempt to create a post with additional attributes (non-fillable)
 $post = new Post([
 'title' => 'Sample Post Title',
 'description' => 'Sample Post Description',
 'image' => 'sample_image.jpg',
 'author' => 'John Doe',
 ]);
 
 // check that the non-fillable attribute is not set on the post instance
 $this->assertArrayNotHasKey('author', $post->getAttributes());
 

This code outlines two methods for testing.

First, you create the Post instance with specified attributes. In addition, using the assertEquals assertion technique, ensures that you set the description, title, and image attributes properly.

The other method tries to make the Post instance with an additional attribute that is not fillable ( author) and asserts that this attribute has not been set in the model instance by using the assertArrayNotHasKey assert method.

  1. Make sure you include the below     use    statement in the identical file:
use App\Models\Post;
  1. Execute the php artisan config:clear operation to remove the configuration cache.
  2. To conduct these tests to run these tests, you must execute this command:
php artisan test tests/Unit/PostModelFunctionalityTest.php

Each test should be passed, and the terminal should show the test results along with the all the time it took to complete the tests.

Tests to debug

If tests fail, you can debug them using these methods:

  1. Review the error message in the terminal. Laravel gives detailed error messages that pinpoint the problem. Take the time to read through the error message for a better understanding of why the test failed.
  2. Examine the code and tests you are testing to identify any discrepancies.
  3. Ensure you properly set up the necessary data and dependencies to run the test.
  4. After you've identified the issue, make the required changes, and then rerun your tests till they are passed.

Databases and tests

  1. Unlock the     phpunit.xml    file and uncomment the following lines of code:

 
  1. Execute the PHP crafter make:test PostCreationTest unit command to make the test scenario.
  2. Open the     tests/Unit/PostCreationTest.php    file and replace the     test_example    The method using this code:
public function testPostCreation()
 
 // Create a new post and save it to the database
 $post = Post::create([
 'title' => 'Sample Post Title',
 'description' => 'Sample Post Description',
 'image' => 'sample_image.jpg',
 ]);
 
 // Retrieve the post from the database and assert its existence
 $createdPost = Post::find($post->id);
 $this->assertNotNull($createdPost);
 $this->assertEquals('Sample Post Title', $createdPost->title);
 
  1. Be sure to include the below     Use     statement:
use App\Models\Post;

Currently, the PostCreationTest class extends the PHPUnitFrameworkTestCase base class. The base class is commonly utilized for unit testing using PHPUnit independently, not in conjunction with Laravel, or when writing tests for a component that is not closely coupled to Laravel. However, you need to access the database, meaning you must modify the postCreationTest class in order to extend it to include the testsTestCase class.

The latter class tailors the PHPUnitFrameworkTestCase class to Laravel applications. It offers additional features and configurations specific to Laravel, such as the seeding of databases and the test environment configuration.

  1. Ensure you replace the use PHPUnitFrameworkTestCase; statement with use TestsTestCase;.Remember that you set the testing environment to use an in-memory SQLite database. Therefore, you need to migrate to the database prior to running tests. Use the IlluminateFoundationTestingRefreshDatabase trait to do this. This trait migrates the database in the event that the schema is not current and then resets the database following every test to make sure that the data from the previous test does not interfere with future tests.
  2. Incorporate the following     use    Statement addressed to     tests/Unit/PostCreationTest.php    File to integrate this attribute within your code
use Illuminate\Foundation\Testing\RefreshDatabase;
  1. Next, add the following code line just prior to the     testPostCreation     method:
use RefreshDatabase;
  1. Run the php artisan config.clear operation to remove the cache of configurations.
  2. For this test to be run, execute the following command:
php artisan test tests/Unit/PostCreationTest.php

The test should be passed, and the terminal must display the test results and the duration of testing.

Feature testing

Although unit tests test each component of the application on its own features tests test more extensive portions of the program, including how several objects interrelate. Tests for feature interaction are essential for several reasons:

  1. End-to-end Validation It confirms that the whole feature functions smoothly, including interactions between the various components, such as controllers, models, views as well as the database.
  2. End-to end testing -- Covers the complete user experience from initial request to last response. It may reveal problems that unit tests may miss. They are a great tool for testing user journeys and complex scenario.
  3. User Experience Assurance is a way to replicate user interaction, which helps verify a consistent user experience and ensures that the feature is working in the way it was intended.
  4. Regression detection -- Catches codes breaking and regressions while the introduction of new software. If an existing feature starts to fail in a test for a feature this indicates that something is broken.

Now, create a feature test for the PostController in the app/Http/Controllers/PostController.php file. It is focused on the storage method of validating incoming data and making and storing the posts in the database.

The test simulates the user making a new post via an online interface. It ensures that the application creates the post into the database and redirects users back to the Posts Index page after creation. To do this, follow these steps:

  1. Use the test PostControllerTest option to make the test case from scratch in the tests/features directory.
  2. Unlock the     tests/Feature/PostControllerTest.php    File and replace it with the     test_example    Method using this code:
use RefreshDatabase; // Refresh the database after each test
 
 public function test_create_post()
 
 // Simulate a user creating a new post through the web interface
 $response = $this->post(route('posts.store'), [
 'title' => 'New Post Title',
 'description' => 'New Post Description',
 'image' => $this->create_test_image(),
 ]);
 
 // Assert that the post is successfully stored in the database
 $this->assertCount(1, Post::all());
 
 // Assert that the user is redirected to the Posts Index page after post creation
 $response->assertRedirect(route('posts.index'));
 
 
 // Helper function to create a test image for the post
 private function create_test_image()
 
 // Create a mock image file using Laravel's UploadedFile class
 $file = UploadedFile::fake()->image('test_image.jpg');
 
 // Return the path to the temporary image file
 return $file;
 

Test_create_post is a function test_create_post function mimics the user making a post from scratch using a POST request to the posts.store route with specific attributes, including a mock image generated using the Laravel uploadedFile class.

It then confirms that the code has successfully saved the post in the database by checking the amount of Post::all(). It confirms that the program redirects the user to the Posts Index page after post creation.

The test will verify that the post-creation functionality works and the application correctly handles the database interactions and redirects after post-submission.

  1. Include the next     Use    statements to the same file:
use App\Models\Post;
 use Illuminate\Http\UploadedFile;
  1. Run the procedure PHP artisan config:clear to clean the configuration cache.
  2. To run this test to test this, run the following command:
php artisan test tests/Feature/PostControllerTest.php

The test should pass, and the terminal must display both the test's results as well as the total time to run the test.

Confirm test coverage

Test coverage refers to the percentage of your codebase that the unit, feature or browser tests test and is expressed in percentage. It can help you determine an area that isn't tested and the under-tested areas potentially having bugs.

The tools PHPUnit's Code Coverage feature, as well as Laravel's built in coverage report can generate reports that reveal the areas of your codebase that your tests test. This provides vital information about your tests' quality and assists you in focusing on areas that may require more tests.

Make an account

  1. Delete the tests/Feature/ExampleTest.php and tests/Unit/ExampleTest.php files, as you have not modified them, and they might cause errors.
  2. Follow the     php artisan test --coverage    A terminal command. You should receive an output that looks like this:
Screen capture showing the execution of the command php artisan test --coverage. It shows the total number of tests that passed and the time to execute the results. It also lists each component in your codebase and its code coverage percentage
Running the command PHP artisan test with a coverage.

The code coverage report shows the results of tests, the total number of tests passed and the length of time it took for executing the test results. The report also lists every component of your codebase along with the percentage of coverage for each component. The percentages indicate the percent of your test code that you cover.

As an example, Models/Post has 100% coverage, meaning that all the model's techniques and codes are included. The report on code coverage will also show how much coverage is available. Total Coverage -- the overall coverage of code across the codebase. The tests cover only 65.3 percent of the code.

  1. For determining a minimum coverage threshold, execute the     php artisan test --coverage --min=85     command. This command sets a minimum threshold of 85%. You should receive the following output:
Screen capture showing the coverage report with a minimum threshold of 85%.The bottom line notes that the test fails because it does not meet the minimum threshold
The test must be conducted with a minimum threshold of 85 %.

The test suites don't pass because the test suites don't reach the required threshold of 85%.

Although achieving greater coverage of code -- often 100% -- is the goal, it's more important to test your application's critical and complex parts thoroughly.

Summary

Incorporating the top practices that are discussed in this article such as writing meaningful thorough tests following the red-green-refactoring cycle of TDD using the test features offered with Laravel and PHPUnit You can build strong and reliable applications.

Jeremy Holcombe

Content & Marketing Editor , WordPress Web Developer, and Content Writer. Apart from everything related to WordPress, I enjoy golf, the beach, as well as movies. Also, I have height issues ;).