Search code examples
phplaravelredismockerylaravel-testing

Mockery Throws Error When Mocking Redis Connection: Mockery\Exception\BadMethodCallException


I have a command and some methods related to Redis connection are running in it. I want to test some of these methods and for this I want to mock the Redis connection. I approached it like this:

protected function mockRedis(): void
{
    $redisConnection = Mockery::mock(Connection::class);
    app()->instance(Connection::class, $redisConnection);

    Redis::shouldReceive('connection')
        ->once()
        ->andReturn($redisConnection);

    Redis::connection()
        ->shouldReceive('client')
        ->once()
        ->andReturnSelf();

    Redis::connection()
        ->client()
        ->shouldReceive('ping')
        ->once()
        ->andReturn(true);

    Redis::connection()
        ->client()
        ->shouldReceive('close')
        ->once();

    Redis::shouldReceive('subscribe')
        ->once()
        ->with(['socket-data'], Mockery::type('callable'))
        ->andReturnUsing(function ($channels, $callback) {
            $message1 = json_encode([
                'type' => 'alive',
                'interval' => 10,
                'time' => '2023-10-04T10:00:00+00:00',
            ]);

            $message2 = json_encode([
                'type' => 'some_type',
                'data' => 'some_data',
            ]);

            $callback($message1);
            $callback($message2);
        });
}

The lines I want to test are as follows:

// some openswoole methods...
// ...
Redis::connection()->client()->ping();

// ...
Redis::connection()->client()->close();

// ...
Redis::subscribe(['socket-data'], function (string $message) {
    //...
});

Caught error:

1) Tests\Feature\RedisSubscribeTest::test_redis_subscribe_command
Mockery\Exception\BadMethodCallException: Received Mockery_0_Illuminate_Redis_Connections_Connection::client(), but no expectations were specified

enter image description here

Any idea?


Solution

  • You should set up the expectations for chained calls using andReturnSelf() and return a new mock for the subsequent chained method.

    protected function mockRedis(): void
    {
        $redisConnection = Mockery::mock(Connection::class);
        app()->instance(Connection::class, $redisConnection);
    
        // Set up the initial Redis::connection() expectation.
        Redis::shouldReceive('connection')
            ->once()
            ->andReturn($redisConnection);
    
        // Set up the chained method calls.
        $redisConnection->shouldReceive('client')
            ->once()
            ->andReturnSelf();
    
        $clientMock = Mockery::mock(); // Create a mock for the client.
    
        // Expect the ping method on the client mock.
        $clientMock->shouldReceive('ping')
            ->once()
            ->andReturn(true);
    
        // Expect the close method on the client mock.
        $clientMock->shouldReceive('close')
            ->once();
    
        // Set up the chained method calls.
        $redisConnection->shouldReceive('client')
            ->once()
            ->andReturn($clientMock);
    
        // Set up the Redis::subscribe expectation.
        Redis::shouldReceive('subscribe')
            ->once()
            ->with(['socket-data'], Mockery::type('callable'))
            ->andReturnUsing(function ($channels, $callback) {
                $message1 = json_encode([
                    'type' => 'alive',
                    'interval' => 10,
                    'time' => '2023-10-04T10:00:00+00:00',
                ]);
    
                $message2 = json_encode([
                    'type' => 'some_type',
                    'data' => 'some_data',
                ]);
    
                $callback($message1);
                $callback($message2);
            });
    }