Generate your fixture file paths easier and less error-prone

Fixture files are a pretty common thing in web development. They allow you to produce "what should be" an end result, and assert that your test case matches that pre-produced end result.

Linking fixture files into your test cases via file paths typically becomes a messy endeavour and is prone to breaking if you ever modify the layout of your tests or fixtures. This often requires a find-and-replace solution in your code editor.

Linking fixtures this way becomes especially difficult when you're testing a Laravel package using Orchestra Testbench, since the file paths will actually be different when you run phpunit and you cannot use base_path() or other Laravel file path helper methods (they will return the path of the test Laravel application, not your actual packages base path).

To illustrate this, let's whip up a fictional test case that processes an audio file and "transcribes" it into a JSON file. Here's our package's file structure:

- package/
  - src/
  - tests/
    - fixtures/
        - transcription_audio.mp3
        - transcription_result.json
    - TranscriptionTest.php
    - TestCase.php
1namespace Vendor\Package\Tests;
2 
3use Vendor\Package\Transcribe;
4 
5class TranscriptionTest extends TestCase
6{
7 public function testTranscriptionWorks()
8 {
9 $audio = __DIR__.'/fixtures/transcription_audio.mp3';
10 
11 $result = file_get_contents(
12 __DIR__.'/fixtures/transcription_result.json'
13 );
14 
15 $this->assertEquals($result, Transcribe::audio($audio));
16 }
17}

There's a couple issues with the above implementation:

  • If we move our fixtures folder, our test will break.

  • If we move TranscriptionTest to a sub-folder, our test will break.

To get around this, we can create a simple Fixture static class responsible for generating file paths to where our fixtures are located, with the help of Laravel's Storage::build() method:

1namespace Vendor\Package\Tests;
2 
3use Illuminate\Support\Facades\Storage;
4 
5class Fixture
6{
7 /**
8 * Get the file path to a fixure.
9 *
10 * @param string $file
11 *
12 * @return string
13 */
14 public static function path($file)
15 {
16 return Storage::build([
17 'driver' => 'local',
18 'root' => implode(DIRECTORY_SEPARATOR, [
19 __DIR__, 'fixtures'
20 ]),
21 ])->path($file);
22 }
23}

Now we can re-write our above test in a much cleaner way:

1namespace Vendor\Package\Tests;
2 
3use Vendor\Package\Transcribe;
4 
5class TranscriptionTest extends TestCase
6{
7 public function testTranscriptionWorks()
8 {
9 $audio = Fixture::path('transcription_audio.mp3');
10 
11 $result = file_get_contents(
12 Fixture::path('transcription_result.json')
13 );
14 
15 $this->assertEquals($result, Transcribe::audio($audio));
16 }
17}

This resolves the above issues:

  1. If we move our TranscriptionTest to a sub-folder, the test will pass

  2. If we move our fixtures to a new folder location, we can update the new file path in one place.

Thanks for reading!