Back to Course |
Testing in Laravel 9 For Beginners: PHPUnit, Pest, TDD

Edit Product: Testing correct values in form inputs

The next obvious thing we would build in such a project is editing the product. The edit form should contain the exact values we will test.


Laravel Code

Before adding the test, we first need an edit form, routes, and controller methods.

app/Http/Controllers/ProductController.php:

use App\Models\Product;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
 
class ProductController extends Controller
{
// ...
 
public function store(StoreProductRequest $request): RedirectResponse
{
Product::create($request->validated());
 
return redirect()->route('products.index');
}
 
public function edit(Product $product): View
{
return view('products.edit', compact('product'));
}
}

routes/web.php:

Route::middleware('auth')->group(function () {
Route::get('products', [ProductController::class, 'index'])->name('products.index');
 
Route::middleware('is_admin')->group(function () {
Route::get('products/create', [ProductController::class, 'create'])->name('products.create');
Route::post('products', [ProductController::class, 'store'])->name('products.store');
Route::get('products/{product}/edit', [ProductController::class, 'edit'])->name('products.edit');
Route::put('products/{product}', [ProductController::class, 'update'])->name('products.update');
});
});

We will test the product update in the next lesson, but the routes must be prepared now because we must use it in the form.

resources/views/products/edit.blade.php:

<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Edit product') }}
</h2>
</x-slot>
 
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="overflow-hidden overflow-x-auto p-6 bg-white border-b border-gray-200">
<div class="min-w-full align-middle">
<form method="POST" action="{{ route('products.update', $product) }}">
@csrf
@method('PUT')
 
<div>
<x-input-label for="name" :value="__('Name')" />
<x-text-input id="name" class="block mt-1 w-full" type="text" name="name" :value="$product->name" required />
<x-input-error :messages="$errors->get('name')" class="mt-2" />
</div>
 
<div class="mt-4">
<x-input-label for="price" :value="__('Price')" />
<x-text-input id="price" class="block mt-1 w-full" type="text" name="price" :value="$product->price" required />
<x-input-error :messages="$errors->get('price')" class="mt-2" />
</div>
 
<div class="flex items-center mt-4">
<x-primary-button>
{{ __('Save') }}
</x-primary-button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

resources/views/products/index.blade.php:

// ...
<table class="min-w-full divide-y divide-gray-200 border">
<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>
@if (auth()->user()->is_admin)
<th class="px-6 py-3 bg-gray-50 text-left">
</th>
@endif
</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>
@if (auth()->user()->is_admin)
<td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
<a href="{{ route('products.edit', $product) }}" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 active:bg-gray-900 focus:outline-none focus:border-gray-900 focus:ring ring-gray-300 disabled:opacity-25 transition ease-in-out duration-150">
Edit
</a>
</td>
@endif
</tr>
@empty
<tr class="bg-white">
<td colspan="2" class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-900">
{{ __('No products found') }}
</td>
</tr>
@endforelse
</tbody>
</table>
// ...

products table with edit button

When we go to the edit page, the values in the inputs will be tested.

edit product form


The Test

Now, let's create a test. In the test, we will first use the Factory to create a product and assign it to a variable. Then acting as admin, we will visit the edit page for that product and, from the response, will make a first assert that the status is 200.

tests/Feature/ProductsTest.php:

class ProductsTest extends TestCase
{
// ...
 
public function test_product_edit_contains_correct_values()
{
$product = Product::factory()->create();
 
$response = $this->actingAs($this->admin)->get('products/' . $product->id . '/edit');
 
$response->assertStatus(200);
}
 
private function createUser(bool $isAdmin = false): User
{
return User::factory()->create([
'is_admin' => $isAdmin,
]);
}
}

Next, because the response returns the HTML, we can use assertSee to assert that there is an input with a product name and price value. We must add a second parameter as false so that quotes won't be escaped and show as special chars.

tests/Feature/ProductsTest.php:

class ProductsTest extends TestCase
{
// ...
 
public function test_product_edit_contains_correct_values()
{
$product = Product::factory()->create();
 
$response = $this->actingAs($this->admin)->get('products/' . $product->id . '/edit');
 
$response->assertStatus(200);
$response->assertSee('value="' . $product->name . '"', false);
$response->assertSee('value="' . $product->price . '"', false);
}
 
private function createUser(bool $isAdmin = false): User
{
return User::factory()->create([
'is_admin' => $isAdmin,
]);
}
}

And let's add another assertion to check that View has variable product with the same values.

tests/Feature/ProductsTest.php:

class ProductsTest extends TestCase
{
// ...
 
public function test_product_edit_contains_correct_values()
{
$product = Product::factory()->create();
 
$response = $this->actingAs($this->admin)->get('products/' . $product->id . '/edit');
 
$response->assertStatus(200);
$response->assertSee('value="' . $product->name . '"', false);
$response->assertSee('value="' . $product->price . '"', false);
$response->assertViewHas('product', $product);
}
 
private function createUser(bool $isAdmin = false): User
{
return User::factory()->create([
'is_admin' => $isAdmin,
]);
}
}

product edit form test


Commit for this lesson