phplaraveltestinglaravel-scoutpestphp

How to test Laravel Scout adds data to indexes and search results match properly?


I'm trying to test my Laravel Scout (Mellisearch) with PestPHP. Testing the model data gets sent to the search index and the search is correct.

For the search, if I were to search user $user = User::search('wanna_coder101')->get();, then it'd return the user model toArray() results. Or is that just this example?

Using PestPHP, with Event::fake(), we can test whether a broadcast would dispatch. I want to do the same thing but with the Search.

it('Test Broadcast Dispatches', function () {
    // Setup fake event
    Event::fake();

    // Dispatch Broadcast
    MyBroadcast::dispatch(auth()->user());

    // Assert Broadcast Correctly Dispatched
    Event::assertDispatched(MyBroadcast::class);
});

The broadcast driver, in test, would automatically use null or log during testing instead of redis.

Can scout do the same, use collection instead, hence can locally test search indexes get sent off and can get data back to test it's the correct response.

Is the only option to use this Laravel Scount testing package?

Versions

"php": "^8.1.0",
"laravel/framework": "^10.0",
"laravel/scout": "^9.8",
"pestphp/pest-plugin-laravel": "^1.4",
"phpunit/phpunit": "^9.5.10",

Solution

  • After reading the Scout package, there's no local driver, only database and collection (which uses the database).

    Use this laravel array scout testing package. Which adds an array driver and a bunch of test methods. Then it's possible to test the model is synced to the search index.

    The search index is the correct data format and to test the search returns the expected results.

    use App\Models\User;
    use App\Models\Quest;
    
    use Sti3bas\ScoutArray\Facades\Search;
    use function Pest\Laravel\{actingAs};
    use Illuminate\Foundation\Testing\RefreshDatabase;
    
    // Necessary to access Laravel testing helpers and database factory stuff
    uses(
        Tests\TestCase::class,
        RefreshDatabase::class
    )->group('search');
    
    // Auto set up a new authed user for each test
    beforeEach(function () {
        actingAs(User::factory()->create());
    });
    
    /**
     * Tests that the quest is synced to the search index
     */
    it('Test Created Quests are Synced to Search Index', function () {
        // Get the current authed user to associate the quest to
        $quest = Quest::factory()->create([
            'user_id' => auth()->id()
        ]);
    
        // Ensure the quest is synced to the search index
        Search::assertSynced($quest);
    });
    
    /**
     * Tests the quest data format in the search index is correct
     */
    it('Test Created Quests Exist in the Search Index', function () {
        // Get the current authed user to associate the quest to
        $quest = Quest::factory()->create([
            'user_id' => auth()->id()
        ]);
    
        // Ensure the quest data format is correct in search index
        Search::assertContains($quest)
            ->assertContains($quest, function ($record) use ($quest) {
                // Note how I'm only using a subset to test instead of $request = $quest->toArray()?
                // That's because the search index adds some extra fields and $quest->toArray() strips some db added fields
                return [
                    'id' => $record['id'],
                    'name' => $record['name'],
                    'description' => $record['description'],
                    'user_id' => $record['user_id'],
                ] == [
                    'id' => $quest->id,
                    'name' => $quest->name,
                    'description' => $quest->description,
                    'user_id' => $quest->user_id,
                ];
            });
    });
    
    /**
     * Tests the search functionality of the Quest model
     */
    it('Test Search Quest Is Correct', function () {
        // Get the current authed user to associate the quest to
        $quest = Quest::factory()->create([
            'user_id' => auth()->id()
        ]);
    
        // Search for the just created quest by name
        $questSearch = Quest::search($quest->name)->where('user_id', $quest->user_id)->get();
    
        // Make sure we only have one result
        expect($questSearch)
            ->toHaveCount(1);
    
        // Make sure search results match the quest we created
        expect($questSearch->first()->toArray())
            ->toMatchArray($quest->toArray());
    });