Follow

Follow

How to upload real files in Laravel Tests

This is a brief guide on how to upload real files - not fakes - when testing with PHPUnit in Laravel.

Elikem Seake-Kwawu's photo
Elikem Seake-Kwawu
ยทMar 18, 2023ยท

4 min read

Table of contents

  • Prerequisites
  • TLDR
  • About
  • Context
  • Testing

Prerequisites

You need to know a thing or two about testing in Laravel.

TLDR

If you're in a hurry, skip to the end.

About

This guide will teach you how to upload real files in Laravel tests, instead of using fakes. You will find it useful when you want to use the data within that particular file. In this guide, I use an Excel spreadsheet so that I can use the rows and columns.

Context

We're working on a feature that extracts data from a row in a spreadsheet and persists it to the database.

Preferably our spreadsheets should be in a common format such as XLSX or CSV. For this project, we are going to use the Spartner Laravel Excel package (formerly Maatwebsite/excel).

I have already created a spreadsheet in Google docs with just a heading row and one single row of data.

I don't think it is necessary to bore you with the details of instantiating the package and so on. That can be done in a future blog post - or in the comments section - whichever comes first. But for now, let's just focus mainly on the testing aspect.

Testing

Let's begin by creating a folder in our Tests directory. We'll store the Excel file here. My directory looks like this:

Next, let's create the test class. You can create it in the tests/Feature namespace.

When it comes to creating test classes, I often like to do it without explicitly naming the classes. I've explained my reasons in a previous article. If you want to use that approach, though, just be aware that it'll affect the way you use the php artisan test --filter command.

That being said, our class will look like this:

<?php

namespace Tests\Feature;

use App\Imports\FinancialNoteImport;
use Illuminate\Http\UploadedFile;
use Tests\TestCase;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;

return new class extends TestCase
{
    use LazilyRefreshDatabase;

    public function test_that_excel_details_are_saved()
    {
        // We will remove this. Don't worry about it.
        $this->markTestIncomplete();
    }
};

I like to use LazilyRefreshDatabase as opposed to RefreshDatabase. They work almost the same way, but this is more efficient.

What we're basically doing in this class is to test that the actual details of our file are being saved. But the question is how do we do this?

If we navigate to the Laravel documentation section on Testing File Uploads, we see a lot of examples about faking an UploadedFile instance. In those examples, we also see that we are encouraged to fake the Storage Facade as well with Storage::fake(). What this means is that we can pretend to create a file of any extension and then proceed to perform a bunch of assertions - like testing that the file has been uploaded and exists in storage etc, etc, etc.

But the documentation really doesn't help if we need to make use of the contents of the file. What we want is a way to use an existing file. If you try to just get a real file from your disk and use it anyways, the test class will never be able to access the file. I once did this in a project and had to scrap it, because I couldn't figure out why it never worked:

$chunks = ['chunk1', 'chunk2', 'chunk3'];

// Loop sending of three chunks in the Assets/ directory
for ($i = 0; $i < 3; $i++) {
    $chunk = file_get_contents(base_path('tests/Assets').'/'.$chunks[$i]);

    $this->actingAs($this->user)
        ->patchJson(route('api.media.upload-chunk'));
}

That was a long time ago and I never needed to do it again... until recently.

This time, however, I decided to take the bull by the horns and dig deep into the Laravel source code for answers. I recommend digging as a last resort if you need to do something that hasn't been documented. Sometimes, creators of frameworks write a lot of methods without documenting them. So dig first, and if you don't find anything, come up with your own solution.

Fortunately, I found something. This is from the Illuminate\Http\Testing\FileFactory class:

This made everything easy for me. It meant I only needed to use this method in my test and I'd be good to go. So I only had to modify my test class. Because Laravel tests only recognize fake uploaded files, this is the way to go if you really need to use your own data within the files:

public function test_that_excel_details_are_saved()
{
    $fileContent = file_get_contents(base_path('tests/Assets/My Spreadsheet.xlsx'));

    // We can either use the same name or another. Doesn't matter.
    // I chose to use "sample"
    $file = UploadedFile::fake()->createWithContent('sample.xlsx', $fileContent);

    // This will process and save the contents of the file.
    // we don't need to go into detail here.
    (new FinancialNoteImport)->import($file);

    $this->assertDatabaseCount('financial_notes', 1);
    $this->assertDatabaseHas('financial_notes', [
        'total_issued_shares' => 1000000,
        'dividends_paid' => 0,
        'weighted_average_cost_of_capital' => 0.18,
        'capital_expenditure' => 2,
        'depreciation' => 4,
        'accumulated_depreciation' => 6,
        'historical_cost_of_assets' => 9,
        'cash_flow_from_operations' => 590,
    ]);
}

Passes with flying colours!๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰

ย 
Share this