Есть такая известная парадигма в ООП — сервис-контейнеры. Суть её в том, чтобы получать реализацию того или иного сервиса, который тебе зачем-то нужен, из некоего контейнера. То есть не задумываясь о деталях, о том, что это за класс и т.д. Обращаешься к контейнеру — а контейнер сам определит, что надо, и вернет нужный сервис. Очевидно, если заранее не знаешь, о чем речь, из такого пояснения ни фига толком не ясно. Обычно эти пояснения иллюстрируются примерами. В доках Ларавела, скажем, используется пример EventPusher-а и его реализации через Redis.
Слабое место подобных примеров, на мой взгляд, в том, что для начинающего разработчика, не очень знакомого с концепцией контейнера и dependency injection, идея замены одной реализации сервиса на другую не очень близка. В голову сразу приходит, что у меня-то сейчас один класс используется — вот этот. Когда надо будет другой, тогда и контейнер сделаю, а пока буду явно обращаться к данному классу. И это, как мне кажется, вполне нормальный ход мыслей.
Поэтому попробую привести чуть более живой (на мой взгляд, опять же) пример, связанный с тестами, показывающий, в каком случае нам понадобится замена реализации сервиса прямо сейчас, а не когда-нибудь потом.
Допустим, вы в своих проектах практикуете TDD. И у вас, помимо всех остальных тестов, проверяющих всё на свете, есть тест создания какой-нибудь модели через API Post, или просто через форму. И вот внезапно вам понадобилось подцепить на эту форму проверку reCAPTCHA против спаммеров. Реализовать саму логику работы во фронте (на Vue.js) и проверку полученного токена в бэке — нетрудно (вот пример).
А вот тест создания модели (и все связанные с этим роутом/формой тесты) — немедленно поломаются. Потому что где вы возьмете валидный ключ капчи для проверки, если он в тестовом режиме не прилетает из фронта?
Можно было бы попытаться организовать прохождение капчи для бэк-энд тестов (хотя я не совсем понял, как это именно в случае reCAPTCHA сделать). Но:
- от этого нет пользы помимо тестирования;
 - у нас нет задачи тестировать гугл-капчу в каждом тесте бэкенда, который проверяет работу методов post/put у каждой защищенной капчей формы;
 - для тестирования внешнего сервиса достаточно одного теста интеграции — что мы верно всё подключили — а остальное есть забота разработчика внешнего сервиса;
 - если каждый тест формы будет проверять ключ капчи через сервис гугла, тесты внезапно станут значительно дольше работать.
 
В общем, представляется что не нужно проходить гугл-капчу на бэк-энде в тестовых целях. Вот тут-то и возникает необходимость в подмене живой капчи — фейковой, чтобы её не проходить. И это как раз неплохо иллюстрирует концепцию сервис-контейнеров и внедрения зависимостей.
Пусть у нас для проверки рекапчи есть вот такое правило валидации:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Recaptcha implements Rule
{
    public function passes($attribute, $value)
    {
        $recaptcha = new RecaptchaCheck();
        // в $value полученный от фронт-энда токен
        return $recaptcha->check($value);
    }
    public function message()
    {
        return 'The reCaptcha token is invalid.';
    }
}
Вызов метода RecaptchaCheck::check() здесь проверяет полученный токен капчи и говорит, правильный он, или нет. Вот этот вызов мы и хотим подменить фейковым, чтобы не долбиться в гуглосервис при каждом запуске тестов, контроллеры которых используют наше новое правило.
Для подмены сначала создадим интерфейс:
namespace App\Rules;
interface RecaptchaCheck
{
    /**
     *
     * Returns true if $token (and captcha) is valid
     *
     * @param string $token
     *
     * @return bool
     */
    public function check($token);
}
Теперь сделаем, чтобы настоящий класс проверки капчи реализовывал этот интерфейс:
namespace App\Rules;
use GuzzleHttp\Client;
class LiveRecaptchaCheck implements RecaptchaCheck
{
    /**
     *
     * Returns true if $token (and captcha) is valid
     *
     * @param string $token
     *
     * @return bool
     */
    public function check($token)
    {
        // с деталями этой реализации можете ознакомиться в статье, приведенной выше
        $client = new Client();
        $response = $client->post('https://www.google.com/recaptcha/api/siteverify', [
            'form_params' => [
                'secret' => config('services.recaptcha.secret'),
                'response' => $token,
            ],
        ]);
        $data = json_decode($response->getBody(), true);
        return $data['success'];
    }
}
Теперь создадим фейковый класс для подмены:
namespace App\Rules;
class FakeRecaptchaCheck implements RecaptchaCheck
{
    /**
     *
     * Returns true if $token (and captcha) is valid
     *
     * @param string $token
     *
     * @return bool
     */
    public function check($token)
    {
        return ($token == 'Valid reCaptcha token');
    }
}
Как мы видим, метод check() фейкового класса будет возвращать TRUE для строки 'Valid reCaptcha token' и FALSE для любых других строк.
Теперь сделаем так, чтобы наше правило валидации получало сервис проверки из контейнера:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class Recaptcha implements Rule
{
    /** @var RecaptchaCheck */
    protected $recaptcha;
    public function __construct()
    {
        // getting reCaptcha check service
        $this->recaptcha = resolve(RecaptchaCheck::class);
    }
    public function passes($attribute, $value)
    {
        return $this->recaptcha->check($value);
    }
    public function message()
    {
        return 'The reCaptcha token is invalid.';
    }
}
Обратите внимание, что для получения сервиса явно используется хелпер resolve(). Он нужен, потому что при использовании в валидаторе (как будет показано ниже) мы добавляем правило вручную через new. Соответственно автоматический резолвер зависимостей в конструкторе здесь не сработает и ничего не подставит. Придётся вручную вставлять объект нужного класса, а этого мы как раз не хотим.
Чтобы контейнер мог зарезолвить наш интерфейс в объект нужного класса, не забудем добавить запись в сервис-провайдер:
class AppServiceProvider extends ServiceProvider
{
    // ...
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind(RecaptchaCheck::class, LiveRecaptchaCheck::class);
    }
}
А использовать созданное правило в валидаторе можно так:
$request->validate([
    // ...
    'recaptcha_token' => ['required', new Recaptcha],
]);
где recaptcha_token — токен, переданный в запросе из фронт-энда (полученный, очевидно, от API рекапчи).
Вот так мы можем подменить реальный класс проверки тестовым:
protected function setUp() {
    parent::setUp();
    $this->app->bind(RecaptchaCheck::class, FakeRecaptchaCheck::class);
}
Теперь все тесты, реализующие (или унаследовавшие) такой метод setUp() будут вместо живой проверки капчи использовать фейковую.
Например, вот так фейковая проверка в тесте не пройдет валидацию:
/**
 * @test
 */
public function invalid_recaptcha_token_results_in_validation_error() {
    $response = $this->json('POST', '/api/my-form', [
        'recaptcha_token' => 'Invalid reCaptcha token',
    ]);
    $response->assertStatus(422); // validation error
    $this->assertArrayHasKey('recaptcha_token', $response->decodeResponseJson()['errors']);
}
А вот такая — пройдет:
/**
 * @test
 */
public function valid_recaptcha_token_passes_validation() {
    $response = $this->json('POST', '/api/my-form', [
        // other required fields...
        'recaptcha_token' => 'Valid reCaptcha token',
    ]);
    $response->assertStatus(200);
}
