Synchro's avatar

Log::spy with channels

I have a FormRequest that logs validation error details:

protected function failedValidation(Validator $validator)
    {
        Log::channel('logistics')->info('Validation failed. Submitted serials:', [
            'contents' => $this->input('contents'),
        ]);
...

In my test:

        Log::spy();
        $this->postJson(
            'logistics/delivery',
            [
                'location_id' => $location->uuid,
                'contents'    => [
                    $case1->serial,
                ],
            ]
        )
            ->assertUnprocessable();
        Log::shouldHaveReceived('info')
            ->withArgs(function ($message, $context) {
                $this->assertTrue(false, json_encode($message));
                $this->assertStringContainsString('Validation failed. Submitted serials:', $message);
                $this->assertArrayHasKey('contents', $context);
                $this->assertEquals([$case1->serial], $context['contents']);
                return true;
            });

This fails in the controller because after being mocked with spy, Log::channel('logistics') returns null, I assume because the mocked Log facade contains no channels, but the net result is that the request only fails when testing. Is there a workaround for this?

0 likes
2 replies
LaryAI's avatar
Level 58

To address the issue of Log::channel('logistics') returning null when using Log::spy(), you need to ensure that the spy method is applied to the specific logging channel you are using in your application. By default, Log::spy() only mocks the default logging channel, not any custom channels you might have defined.

Here's how you can modify your test to spy on the specific logistics channel:

  1. Use Log::channel('logistics')->spy() to mock the logistics channel.
  2. Ensure that you are checking the logs on the logistics channel in your assertions.

Here is the updated test code:

use Illuminate\Support\Facades\Log;

public function testValidationFailureLogsCorrectly()
{
    // Spy on the 'logistics' channel
    Log::channel('logistics')->spy();

    $location = factory(Location::class)->create();
    $case1 = factory(Case::class)->create();

    $this->postJson(
        'logistics/delivery',
        [
            'location_id' => $location->uuid,
            'contents'    => [
                $case1->serial,
            ],
        ]
    )
    ->assertUnprocessable();

    Log::channel('logistics')->shouldHaveReceived('info')
        ->withArgs(function ($message, $context) use ($case1) {
            $this->assertStringContainsString('Validation failed. Submitted serials:', $message);
            $this->assertArrayHasKey('contents', $context);
            $this->assertEquals([$case1->serial], $context['contents']);
            return true;
        });
}

Explanation:

  1. Spying on the Specific Channel: Log::channel('logistics')->spy(); ensures that the logistics channel is being mocked.
  2. Assertions on the Specific Channel: Log::channel('logistics')->shouldHaveReceived('info') ensures that the assertions are made on the logistics channel.

By making these changes, you ensure that the logistics channel is properly mocked and that your test can correctly verify the log entries.

Synchro's avatar

@LaryAI That all sounds logical, unfortunately, when I call spy on the channel instead of the facade, it results in this, which looks like it's the result of Larry hallucinating:

Error: Call to undefined method Monolog\Logger::spy()

Please or to participate in this conversation.