Laravel: как смоделировать методы класса внедрения зависимостей - PullRequest
3 голосов
/ 21 февраля 2020

Я использую GitHub API через Laravel API Wrapper . Я создал класс внедрения зависимости. Как я могу издеваться над методом exists в классе App\Http\GitHub.php?

App\Http\GitHub.php:

use GrahamCampbell\GitHub\GitHubManager;

class Github
{
    public $username;

    public $repository;

    public function __construct($username, $repository, GitHubManager $github)
    {
        $this->username = $username;

        $this->repository = $repository;

        $this->github = $github;
    }

    public static function make($username, $repository)
    {
        return new static($username, $repository, app(GitHubManager::class));
    }

    /**
     * Checks that a given path exists in a repository.
     *
     * @param  string  $path
     * @return bool
     */
    public function exists($path)
    {
        return $this->github->repository()->contents()->exists($this->username, $this->repository, $path);
    }
}

Тест:

    use App\Http\GitHub;
    public function test_it_can_check_if_github_file_exists()
    {
        $m = Mockery::mock(GitHub::class);
        $m->shouldReceive('exists')->andReturn(true);
        app()->instance(GitHub::class, $m);

        $github = GitHub::make('foo', 'bar');

        $this->assertTrue($github->exists('composer.lock'));
    }

Запуск этого теста на самом деле бьет по API, а не просто возвращает поддельное true значение, что я тут не так делаю?

Ответы [ 2 ]

1 голос
/ 21 февраля 2020

Здесь есть проблемы с деревом, как вы создаете объект. То, как вы вызываете два метода для своего фиктивного объекта и привязываете его к неправильному экземпляру.

Внедрение зависимостей

Stati c методы в целом является Параметры anti pattern и constructor не работают с тем, как работает контейнер, поэтому вы не сможете использовать resolve(Github::class);. Обычно Laravel классы решают эту проблему с помощью сеттеров.

class Github
{
    public $username;

    public $repository;

    public $github;

    public function __construct(GitHubManager $github)
    {
        $this->github = $github;
    }

    public function setUsername(string $username) {
        $this->username = $username;

        return $this;
    }

    public function setRepository(string $repository) {
        $this->repository = $repository;

        return $this;
    }
}

Теперь вы можете вызывать код с помощью следующего подхода.

resolve(Github::class)->setUsername('Martin')->setRepository('my-repo')->exists();

Цепочка методов

Здесь есть два вызова фиктивного объекта, они цепочки, поэтому вы должны создать фиктивную цепочку, подобную этой. Прямо сейчас фиктивный объект не будет знать содержимого и, следовательно, потерпит неудачу.

$m = Mockery::mock(GitHub::class);

$m->shouldReceive('contents')
    ->andReturn($m);

$m->shouldReceive('exists')
    ->with('Martin', 'my-repo', 'your-path')
    ->once()
    ->andReturn(true);

Привязка экземпляра

Работая с контейнером, он автоматически загрузит его на основе классы, поэтому следующий код будет вводить зависимости GithubManager, если разрешается с помощью app(), resolve() или в конструкторе.

public function __construct(GithubManager $manager)

Этот код будет внедрять GithubManager в моем примере разрешения выше, но в вашем Например, вы привязываете его к классу GitHub, который не загружается автоматически, и вы всегда должны издеваться над классом, который находится дальше всего по цепочке. Для этого экземпляр привязки должен быть.

app()->instance(GitHubManager::class, $m);
1 голос
/ 21 февраля 2020

Ваша проблема в том, что когда вы инициализируете свой объект github, вы не ссылаетесь на объект в Служебном контейнере .

        // Initialises an object in the service container.
        app()->instance(GitHub::class, $m);

        // Creates a new object from the class and doesn't use the one in the container.
        $github = GitHub::make('foo', 'bar');

Служебный контейнер по сути представляет собой ящик со всеми вашими инициализированными объекты в нем, и вы можете ссылаться на них в любое время в течение жизненного цикла Laravel. Этот шаблон позволяет нам делать такие вещи, как Dependency Injection чистым способом, и в результате мы можем проверять, когда классы вызываются, потому что мы можем «поменять» то, что в коробке, с тем, что мы хотим.

Laravel абстрагировал все вышеперечисленное для нас, используя насмешливые функции . Лично я просто использую шпиона для всего, поэтому мне не нужно помнить, что делают другие (если есть ситуации, когда вам нужно использовать других).

Теперь для решения:

    public function test_it_can_check_if_github_file_exists()
    {
        // Initialise GitHub::class into the service container
        $gitHubSpy = $this->spy(GitHub::class);

        // Mock the function
        $gitHubSpy->shouldReceive('exists')
            ->andReturn(true);

        // Assert we have mocked correctly
        $this->assertTrue($gitHubSpy->exists('composer.lock'));
    }

В реальной ситуации вы, скорее всего, захотите заявить, что ваш производственный код называется функцией, которую вы можете выполнить, выполнив:

$gitHubSpy->shouldReceive('exists')->with('composer.lock')->andReturn(true);
...