I am a complete novice in testing and I wanted to know if someone could guide me on what I'm doing wrong. I want to test the following function in my php laravel project:
public static function getFlatSupplierAddress($supplier_address_id): string
{
$supplierAddress = Address::findOrFail($supplier_address_id);
return $supplierAddress->street . " " .
$supplierAddress->number . " " .
$supplierAddress->apartment . ", " .
($supplierAddress->city ? $supplierAddress->city->name : '') . ", " .
($supplierAddress->province ? $supplierAddress->province->name : '') . ", " .
($supplierAddress->country ? $supplierAddress->country->name : '');
}
This is my test code:
it('returns the formatted supplier address', function () {
$addressMock = Mockery::mock(Address::class);
$addressMock->shouldReceive('findOrFail')
->with(1)
->andReturnSelf(); // Retorna el mismo mock
$addressMock->shouldReceive('getAttribute')
->with('street')
->andReturn('Calle');
$addressMock->shouldReceive('getAttribute')
->with('number')
->andReturn('456');
$addressMock->shouldReceive('getAttribute')
->with('apartment')
->andReturn('Apt 789');
$addressMock->shouldReceive('city')
->andReturn((object)['name' => 'Retiro']);
$addressMock->shouldReceive('province')
->andReturn((object)['name' => 'Buenos Aires']);
$addressMock->shouldReceive('country')
->andReturn((object)['name' => 'Argentina']);
$expectedAddress = 'Calle 456 , Retiro, Buenos Aires, Argentina';
$formattedAddress = AddressService::getFlatSupplierAddress(1);
// Assert
expect($formattedAddress)->toBe($expectedAddress);
Of course I have the problem that I am not using well the Mock since the function returns me a string of an address that it found in the database and it is comparing it with the Mock that I have created.
You don't need to mock anything in order to test that
Edit a somewhat more in depth explanation
As I commented,
findOrFailis not actually a method found inAddress. Laravel does this a lot. It forwards calls to other classes under the hood by using__callor__callStatic.When you do
Address::findOrFail(1), it actually calls the underlyingIlluminate\Database\Eloquent\Model's static__callStatic('findOrFail', [1]).... which in turn just does
(new static)->findOrFail(1)... which in turns calls the
Model's__call('findOrFail' [1])... which in turn eventually calls the
Model'snewQuery()and returns anIlluminate\Database\Eloquent\Builder... which finally calls
findOrFail(1).And that just covers
findOrFail. Whenever you call aModel's attributes ($address->street,$address->city->name, etc), it also forwards the call to other methods via__call.If you really wanted to write this test with mocks and make sure it didn't touch the database, I suppose you could write something like this:
As an aside, if you want to test every combiantion of having or not having a
City,ProvinceandCountryin yourAddress, that's 8 separate cases. You could take advantage of bitwise operations to avoid having to write the same test 8 times.Or, if you don't care about writing to the testing database