php PEST测试

k4emjkb1  于 2023-09-29  发布在  PHP
关注(0)|答案(1)|浏览(165)

我在测试方面完全是个新手,我想知道是否有人可以指导我做错了什么。我想在我的php laravel项目中测试以下函数:

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

这是我的测试代码:

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

当然,我有一个问题,我没有很好地使用Mock,因为函数返回给我一个在数据库中找到的地址字符串,并将其与我创建的Mock进行比较。

kkih6yb8

kkih6yb81#

你不需要嘲笑任何东西来测试它

it('returns the formatted supplier address', function () {
    // Arrange
    $address = Address::factory()
      ->for(City::factory())
      ->for(Province::factory())
      ->for(Country::factory())
      ->create();

    // Act
    $formatted = getFlatSupplierAddress($address->id);

    // Assert
    $expected = "{$address->street} {$address->number} {$address->apartment} {$address->city?->name}, {$address->province?->name}, {$address->country?->name}";

    $this->assertEquals($formatted, $expected);
    // or expect($formatted)->toBe($expected);
});

编辑更深入的解释

正如我所评论的,findOrFail实际上不是Address中的方法。Laravel经常这么做。它通过使用__call__callStatic将调用转发到后台的其他类。
当执行Address::findOrFail(1)时,它实际上调用了底层Illuminate\Database\Eloquent\Model的静态__callStatic('findOrFail', [1])
...这反过来又做了(new static)->findOrFail(1)
.它依次调用Model__call('findOrFail' [1])
.它最终调用ModelnewQuery()并返回Illuminate\Database\Eloquent\Builder
... whichfinallycalls findOrFail(1) .
而这仅仅涵盖了findOrFail。无论何时调用Model的属性($address->street$address->city->name等),它都会通过__call将调用转发给其他方法。
如果你真的想用mocks来写这个测试,并确保它不接触数据库,我想你可以写这样的东西:

use App\Models\{Address, Country, City, Province};
use Illuminate\Database\Eloquent\Builder;

it('returns the formatted supplier address', function () {
    // Arrange
    $address = Address::factory()->make();
    $address->setRelation('city', City::factory()->make());         // Have to set the relations like this
    $address->setRelation('province', Province::factory()->make()); // because the Factory's for method will persist them 
    $address->setRelation('country', Country::factory()->make());   // into the database otherwise
    $address->id = 'fake-id'; // value doesn't really matter.

    // Mock
    $builderMock = Mockery::mock(Builder::class);
    $builderMock->shouldReceive('findOrFail')->with($address->id)->andReturn($address);
    $addressMock = Mockery::mock(Address::class)->makePartial(); // we still need the class's original behavior for some methods.
    $addressMock->shouldReceive('newQuery')->andReturn($builderMock);

    // Act
    $formatted = getFlatSupplierAddress($address->id);

    // Assert
    $expected = "{$address->street} {$address->number} {$address->apartment} {$address->city?->name}, {$address->province?->name}, {$address->country?->name}";

    $this->assertEquals($formatted, $expected);
    // or expect($formatted)->toBe($expected);
});

顺便说一句,如果你想测试Address中有或没有CityProvinceCountry的每一个组合,那就是8个不同的情况。您可以利用按位操作来避免编写8次相同的测试。

it('returns the formatted supplier address', function ($i) {
    $address = Address::factory()->create();
    if ($i & 0b001) $address->setRelation('city', City::factory()->make());
    if ($i & 0b010) $address->setRelation('province', Province::factory()->make()); 
    if ($i & 0b100) $address->setRelation('country', Country::factory()->make());
})->with(range(0b000, 0b111))

或者,如果您不关心写入测试数据库

it('returns the formatted supplier address', function ($i) {
    $factory = Address::factory();
    if ($i & 0b001) $factory = $factory->for(City::class);
    if ($i & 0b010) $factory = $factory->for(Province::class); 
    if ($i & 0b100) $factory = $factory->for(Country::class);
    $address = $factory->create();
})->with(range(0b000, 0b111))

相关问题