thatsus / laravel-better-bind
A better bind feature for automated tests in Laravel
Requires
- php: >=7.0.0
- illuminate/support: ^5.0,<5.6
Requires (Dev)
- orchestra/testbench: ~3.0
- php-mock/php-mock-mockery: ^1.1
- phpunit/phpunit: ~5.7
This package is auto-updated.
Last update: 2024-04-18 07:45:59 UTC
README
A better bind feature for automated tests in Laravel/Lumen 5+.
Why Better Bind is better
- It's less verbose than Laravel's built-in option.
- It protects you against missing constructor parameters.
- You can choose to check the constructor parameters for correctness.
Automated testing in Laravel using mocks means injecting objects using the
Application's bind
method.
This can have some drawbacks.
App::bind
is verbose in Laravel.App::bind
doesn't test that an object is instantiated with the right parameters.
Better Bind provides a syntactically friendly mechanism to verify that constructor parameters match your target class.
Missing required parameters cause an assertion failure.
Extra parameters cause an assertion failure.
Wrong-type parameters cause an assertion failure.
It can be a one-liner.
Better Bind also provides a way to capture the constructor parameters so you can run your own assertions on them.
Installation
composer require thatsus/laravel-better-bind
class TestCase { use \ThatsUs\BetterBind; ... }
Example Test
In this example we expect the Dog::bark
method to create a Sound
object with
itself as the value for the constructor's $animal
parameter.
First, we use the betterInstance
method from Better Bind to provide the mock to
the code. Then we capture the $params
argument and check at the end that it
has the parameter values we expect.
class DogTest extends TestCase { public function testBark() { $mock = Mockery::mock(Sound::class); $mock->shouldReceive('emit') ->once(); $this->betterInstance(Sound::class, $mock, $params); $dog = new Dog(); $dog->bark(); $this->assertEquals(['animal' => $dog], $params); } }
class Sound { public function __construct(Animal $animal) { // ... } }
Passing Code
Gallant wrote this code correctly with the right parameters for the constructor.
class Dog { public function bark() { App::makeWith(Sound::class, ['animal' => $this])->emit(); } }
Failing Code 1
Goofus forgot to include the name of the parameter for the constructor.
class Dog { public function bark() { App::makeWith(Sound::class, [$this])->emit(); } }
1) DogTest::testBark
Required parameter `animal` not provided to class constructor for `Sound`
Failing Code 2
Now Goofus included an extra parameter for the constructor by accident.
class Dog { public function bark() { App::makeWith(Sound::class, ['animal' => $this, 'volume' => 'loud'])->emit(); } }
1) DogTest::testBark
Extra parameters provided to class constructor for `Sound`: `volume`
Failing Code 3
Goofus accidentally passed in a string. That's the wrong type for the $animal
parameter.
class Dog { public function bark() { App::makeWith(Sound::class, ['animal' => 'this'])->emit(); } }
1) DogTest::testBark
Required parameter `animal` for `Sound` is a `string`, but a `Animal` is expected.
Methods
betterInstance($signature, $object, [&$params = []])
- $signature - string, the class name or other string requested in a
makeWith
call. - $object - mixed, the value to return from the
makeWith
call. - $params - optional, when
makeWith
is called, this variable will be updated with the parameters sent tomakeWith
so that your test code may make assertions against them.
If $signature
is a string that does not refer to an existing class, no
assertions will run against the parameters.
betterBind($signature, $closure, [&$params = []])
- $signature - string, the class name or other string requested in a
makeWith
call. - $closure - a closure that will be called when
makeWith
is called. The return value will be returned frommakeWith
. The parameters will be$app
, the Application object, and$params
, the array of parameters sent tomakeWith
. - $params - optional, when
makeWith
is called, this variable will be updated with the parameters sent tomakeWith
so that your test code may make assertions against them.
If $signature
is a string that does not refer to an existing class, no
assertions will run against the parameters.
betterBind(...)->ignoreParameters($param1, $param2, ...)
Allow Laravel to supply the parameters named. Do not check that the given
parameter names are given by the call to makeWith
.
- $paramN - string, the name of a parameter
I'm not convinced. Can't I do this without Better Bind?
You can do some of the same stuff without this library.
Compare the following versions of the test.
class DogTest extends TestCase { public function testBark() { $mock = Mockery::mock(Sound::class); $mock->shouldReceive('emit') ->once(); $this->betterInstance(Sound::class, $mock, $params); $dog = new Dog(); $dog->bark(); $this->assertEquals(['animal' => $dog], $params); } }
class DogTest extends TestCase { public function testBark() { $mock = Mockery::mock(Sound::class); $mock->shouldReceive('emit') ->once(); App::bind(Sound::class, function ($app, $bind_params) use (&$params, $mock) { $params = $bind_params; return $mock; }); $dog = new Dog(); $dog->bark(); $this->assertEquals(['animal' => $dog], $params); } }
The obvious drawback to the version that doesn't use Better Bind is that there
are extra lines, and one of them is very verbose. The secret extra drawback
here is that nothing tests to ensure that the requirements to the real Sound
class's constructor are met.
What will happen if you write the code from "Failing Code 1"?
class Dog { public function bark() { App::makeWith(Sound::class, [$this])->emit(); } }
Laravel will detect that Sound
's constructor typehints an Animal
object.
But no 'animal' element is in the params, so Laravel will new up an Animal
object to do the job. There will be no test failure.
Using Better Bind, the missing value will be detected and the test will fail.
If you want Laravel to fill in a new Animal
object itself, you can add
->ignoreParameters('animal')
.
Compatibility
We believe this code is compatible with all versions of Laravel 5+ and Lumen
5+, but testing it is a chore because make
changed to makeWith
in 5.4 just
to confuse everybody. Drop us a line and let us know how great it is and what
version of Laravel you're working with.
Contribution
If you find a bug or want to contribute to the code or documentation, you can help by submitting an issue or a pull request.