Home » Php » php – In Laravel, how to give another implementation to the service container when testing?

php – In Laravel, how to give another implementation to the service container when testing?

Posted by: admin July 12, 2020 Leave a comment

Questions:

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!

How to&Answers:

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