In Laravel, how to give another implementation to

2019-04-10 13:39发布

问题:

I'm creating a Laravel controller where a Random string generator interface gets injected to one of the methods. Then in AppServiceProvider I'm registering an implementation. This works fine.

The controller uses the random string as input to save data to the database. Since it's random, I can't test it (using MakesHttpRequests) like so:

$this->post('/api/v1/do_things', ['email' => $this->email])
->seeInDatabase('things', ['email' => $this->email, 'random' => 'abc123']);

because I don't know what 'abc123' will be when using the actual random generator. So I created another implementation of the Random interface that always returns 'abc123' so I could assert against that.

Question is: how do I bind to this fake generator at testing time? I tried to do

$this->app->bind('Random', 'TestableRandom');

right before the test, but it still uses the actual generator that I register in AppServiceProvider. Any ideas? Am I on the wrong track completely regarding how to test such a thing?

Thanks!

回答1:

You have a couple options:

Use a conditional to bind the implementation:

class AppServiceProvider extends ServiceProvider {

    public function register() {
        if($this->app->runningUnitTests()) {
           $this->app->bind('Random', 'TestableRandom');
        } else {
           $this->app->bind('Random', 'RealRandom');
        }
    }
}

Second option is to use a mock in your tests

public function test_my_controller () {
    // Create a mock of the Random Interface
    $mock = Mockery::mock(RandomInterface::class);

    // Set our expectation for the methods that should be called
    // and what is supposed to be returned
    $mock->shouldReceive('someMethodName')->once()->andReturn('SomeNonRandomString');

    // Tell laravel to use our mock when someone tries to resolve
    // an instance of our interface
    $this->app->instance(RandomInterface::class, $mock);

    $this->post('/api/v1/do_things', ['email' => $this->email])
         ->seeInDatabase('things', [
             'email' => $this->email, 
             'random' => 'SomeNonRandomString',
         ]);
}

If you decide to go with the mock route. Be sure to checkout the mockery documentation:

http://docs.mockery.io/en/latest/reference/expectations.html