In this lesson, let's talk about Unit Tests in addition to the Feature Tests. Unit is some internal code of your application, so Unit tests are supposed to test some features and functions in terms of code. It doesn't load the page or the API, doesn't use the full feature, but instead tests that some internal method returns correct data.
For this example, we want to show additional prices for euros in the table.
To show the price in euros, there is a converter. This is going to be a fake converter. Imagine a CurrencyService
class in the App\Services
directory. In the service, there is a function to convert the price.
app/Services/CurrencyService.php:
namespace App\Services; class CurrencyService{ const RATES = [ 'usd' => [ 'eur' => 0.98 ], ]; public function convert(float $amount, string $currencyFrom, string $currencyTo): float { $rate = self::RATES[$currencyFrom][$currencyTo] ?? 0; return round($amount * $rate, 2); }}
In the Product
Model, we add an eloquent accessor.
app/Models/Product.php:
use App\Services\CurrencyService;use Illuminate\Database\Eloquent\Casts\Attribute; class Product extends Model{ protected $fillable = [ 'name', 'price', ]; protected function priceEur(): Attribute { return Attribute::make( get: fn() => (new CurrencyService())->convert($this->price, 'usd', 'eur'), ); } }
And we show this field in the View.
resources/views/products/index.blade.php:
// ...<thead><tr> <th class="px-6 py-3 bg-gray-50 text-left"> <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Name</span> </th> <th class="px-6 py-3 bg-gray-50 text-left"> <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Price (USD)</span> </th> <th class="px-6 py-3 bg-gray-50 text-left"> <span class="text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">Price (EUR)</span> </th> </tr></thead> <tbody class="bg-white divide-y divide-gray-200 divide-solid"> @forelse($products as $product) <tr class="bg-white"> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $product->name }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ number_format($product->price, 2) }} </td> <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900"> {{ $product->price_eur }} </td> </tr>// ...
The overall feature test would check if the page loads successfully with those values and the products. But it would be cool to test if the service converter works well with different currencies and values. Then, it becomes like a separate black box test.
Let's create a new Unit test.
php artisan make:test CurrencyTest --unit
You can delete the example unit test.
In this test, we must call our CurrencyService
service. This service will return some value. So, in our test, we can assert that some value is equal to other.
tests/Unit/CurrencyTest.php:
use PHPUnit\Framework\TestCase;use App\Services\CurrencyService; class CurrencyTest extends TestCase{ public function test_convert_usd_to_eur_successful() { $this->assertEquals(98, (new CurrencyService())->convert(100, 'usd', 'eur')); }}
Now, let's run the tests.
As you can see, we have convert usd to eur successful
under unit tests passed.
From here, it is your decision for what cases to test.
Let's add another unit test. The expected result is 0 this time because the gbp
isn't set in the service.
tests/Unit/CurrencyTest.php:
class CurrencyTest extends TestCase{ public function test_convert_usd_to_eur_successful() { $this->assertEquals(98, (new CurrencyService())->convert(100, 'usd', 'eur')); } public function test_convert_usd_to_gbp_returns_zero() { $this->assertEquals(0, (new CurrencyService())->convert(100, 'usd', 'gbp')); } }