I'm making a course on Laravel Sanctum: MasteringAuth.com

Testing file uploads

Constantin Druc ยท 15 May, 2021

[00:01] Let's take a look over how we can test file uploads in laravel. I'm starting from a clean slate so I don't have a route, controller, not even a model. We'll create those as we go.

[00:13] The example I have in mind is uploading some kind of pdf report. So let's create a reports controller test: php artisan make:test ReportsControllerTest. Let's remove the example test and write a new one called store_report.

[00:35] For this test we'll need an authenticated user, then we'll have to make a request with file, and finally, we need to assert that the report was created and the file was stored.

[00:52] Let's tackle these one by one. First we can create a user using the user factory, and then we can sign it in using $this->actingAs($user). So now we have an authenticated user.

[01:11] For the next step we need a file to send along with the request. We can create the test file using Laravel's testing file class. So, file, and we'll grab this one under testing, create, we'll pass the name of the file, which will be report.pdf and then the size in kilobytes, let's say 100. And now we can send the post request. So this post route reports dot; this route doesn't exist yet but we'll create it, and we'll send name which will be "sales" and file which will be our file we just created.

[01:50] Now for the third step we can add our first assertion which will be $this->assertDatabaseHas('reports', ['name' => 'sales']) and we'll look for a report with name equals sales. Now let's make sure we are using the RefreshDatabase trait and run the test.

[02:12] It will fail because the report.store route is not defined. So let's go inside web.php and define the route: post reports and we'll need a ReportsController with a store action, and we'll name this route; name reports.store. Of course this controller doesn't exist so let's create it: php artisan make:controller ReportsController.

[02:51] Now import the controller and add the store method. So public function store. If we run the test again it will say that there is no such table reports, so let's create a report model: php artisan make:model Report -m and then dash m to create the report migration file.

[03:15] we'll open the migration file and add two more columns: one string column for the name, and another one for the file path. If we run the test again it will fail because there are no rows in the reports table. So let's go inside the ReportsController and do Report::create() and here we'll pass the name from the request, and in order to grab the path we need to store the file on disk; and we can do that by calling the store method, pass the name of the directory, and the name of the disk.

[03:55] Now if we rerun the test it still... fails but I think it's because we haven't set the name and path as fillable attributes. But to make sure let's disable exception handling and we can do that with $this->withoutExceptionHandling(). Rerun the test and here it is. We need to add name and path to the fillable property of the report model.

[04:24] So we'll go to report and say fillable name and path; either we do this or we can do protected guarded and set it to an empty array. These two are the same.

[04:43] Let's rerun the test and it passes. However, we don't have any assertions in place that will make sure our file was actually stored on disk so let's add them.

[04:54] The first thing I want to check is that indeed, path has a value, and we can do so by grabbing the report from the database and assert that the path is not null. So $this->assertNotNull($report->path). Let's rerun the test, and it passes. Then I want to make sure that this path points to an existing file on disk, but in order to do so we need to swap the current file system with a fake, and Laravel provides a Storage class to do that. We need to add here at the beginning of our test: storage fake and then the name of the disk we want to fake, local and then we can assert that the path really points to an existing file; and we can do that with storage disk pass the name of the disk, which is local, and then assert exists, and we need to pass in the report path. We run the test and it surely passes.

[06:02] And finally we want to make sure that the file we wanted to upload matches the one we find on disk, and we can do that with the following assertion: $this->assertFileEquals($file). This will make sure that the files are the same, and it passes.

[06:32] One small optimization we can make is to replace this assertion with another one made on the report object. This way we execute only one query not two. So this assert equals sales report name, and we can remove this one. Rerun the tests and it still passes.

[07:01] Let's revisit this test again. So we swap the current file system with a fake; that will allow us to make assertions, then we authenticate a user, we make a request using a file we have created, and then we assert that the report was stored in the database, that a file exists on the report path, and that the existing file matches the file we've uploaded.

[07:29] And that's it that's how you can test file uploads in Laravel applications.