Laravel: how to mock dependency injection class methods

hinteractive02 :

I'm using the GitHub API through a Laravel API Wrapper. I've created a dependency injection class. How can I mock the exists method within the App\Http\GitHub.php class?

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);
    }
}

Test:

    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'));
    }

Running this test actually hits the API rather than just returning the mocked true value, what am I doing wrong here?

mrhn :

There is tree problems here, the way you instantiate your object. The way you are calling two methods on your mock object and you are binding it to the wrong instance.

Dependency injection

Static methods in general is an anti pattern and constructor parameters does not work with how the container works, therefor you would not be able to use resolve(Github::class);. Usually Laravel classes solve this by using setters.

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;
    }
}

Now you can call your code with the following approach.

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

The chaining of methods

Here there are two calls to the mock object, they are chaining, so you should create a mock chain similar to this. Right now the mock object would not know contents and therefor fail.

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

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

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

Binding the instance

Working with the container, it will automatically load it based on the classes, so the following code will dependency inject GithubManager if resolved with app(), resolve() or in a constructor.

public function __construct(GithubManager $manager)

This code will inject GithubManager in my resolve example above, but in your example you are binding it to the GitHub class, which wont automatically load and you should always mock the class farthest down the chain. Therefor you instance bind should be.

app()->instance(GitHubManager::class, $m);

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=9732&siteId=1