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

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:

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

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

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

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!