Now, let's see how we can test the users and the access of logged-in users.
First, we must add a Middleware auth
to the route.
routes/web.php:
Route::get('/', function () { return view('home');})->name('home'); Route::resource('products', ProductController::class)->middleware('auth');
After running the tests, we have three failed Feature tests. The two passed tests are Unit tests.
We need to create a user using Factories for each test function and perform the server request using that user.
For that, there is a method called actingAs()
, which accepts the User Model as a parameter.
tests/Feature/ProductsTest.php:
use function Pest\Laravel\actingAs; test('homepage contains empty table', function () { $user = User::factory()->create(); actingAs($user) ->get('/products') ->assertStatus(200) ->assertSee(__('No products found'));}); test('homepage contains non empty table', function () { $user = User::factory()->create(); $product = Product::create([ 'name' => 'Product 1', 'price' => 123, ]); actingAs($user) ->get('/products') ->assertStatus(200) ->assertDontSee(__('No products found')) ->assertSee('Product 1') ->assertViewHas('products', function (LengthAwarePaginator $collection) use ($product) { return $collection->contains($product); });}); test('paginated products table doesnt contain 11th record', function () { $user = User::factory()->create(); $products = Product::factory(11)->create(); $lastProduct = $products->last(); actingAs($user) ->get('/products') ->assertStatus(200) ->assertViewHas('products', function (LengthAwarePaginator $collection) use ($lastProduct) { return $collection->doesntContain($lastProduct); });});
Now we have all tests green again.
Until now, we have tested one scenario in which logged-in users can access the page. We also need to test the opposite case: unauthenticated users cannot access this page.
For this, let's create a general Auth test with a few methods.
php artisan make:test AuthTest
Notice: you can structure your test files however you want, but it's a common practice to separate files by "topic" like AuthTest
, or by features like RegistrationTest
, with one or more methods inside each test file.
In this test class, we need to simulate going to the /products
page without any user and assert two things:
/login
URL.tests/Feature/AuthTest.php:
use function Pest\Laravel\get; test('unauthenticated user cannot access product', function () { get('/products') ->assertStatus(302) ->assertRedirect('login');});
Now, we have six tests passed.
In the final test for this lesson, we will test a login form. Also, after successful login, we need to test that the user is redirected to the products page.
In this test, first, we must create a new user. Then, instead of a GET request, we will send a POST request and pass parameters as an array as in the form. Finally, assert the response status and redirect URL.
tests/Feature/AuthTest.php:
// ... test('login redirects to products', function () { User::create([ 'name' => 'User', 'email' => 'user@user.com', 'password' => bcrypt('password123') ]); post('/login', [ 'email' => 'user@user.com', 'password' => 'password123' ]) ->assertStatus(302) ->assertRedirect('products');});
By default, Breeze redirects to the /dashboard
URL after successful authentication. We must change its value in the AuthenticatedSessionController
.
app/Http/Controllers/Auth/AuthenticatedSessionController.php:
class AuthenticatedSessionController extends Controller{ // ... public function store(LoginRequest $request): RedirectResponse { $request->authenticate(); $request->session()->regenerate(); return redirect()->intended(route('dashboard', absolute: false)); return redirect()->intended(route('products.index', absolute: false)); } // ...}
And we have seven green tests.
With PHPUnit, we have identical syntax but with the usage of $this
.
tests/Feature/ProductsTest.php:
use App\Models\User; class ProductsTest extends TestCase{ use RefreshDatabase; public function test_homepage_contains_empty_table(): void { $user = User::factory()->create(); $response = $this->get('/products'); $response = $this->actingAs($user)->get('/products'); $response->assertStatus(200); $response->assertSee(__('No products found')); } public function test_homepage_contains_non_empty_table(): void { $product = Product::create([ 'name' => 'Product 1', 'price' => 123, ]); $user = User::factory()->create(); $response = $this->get('/products'); $response = $this->actingAs($user)->get('/products'); $response->assertStatus(200); $response->assertDontSee(__('No products found')); $response->assertSee('Product 1'); $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($product) { return $collection->contains($product); }); } public function test_paginated_products_table_doesnt_contain_11th_record() { $products = Product::factory(11)->create(); $lastProduct = $products->last(); $user = User::factory()->create(); $response = $this->get('/products'); $response = $this->actingAs($user)->get('/products'); $response->assertStatus(200); $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($lastProduct) { return $collection->doesntContain($lastProduct); }); }}
Test to check if guests cannot access products:
tests/Feature/AuthTest.php:
class AuthTest extends TestCase{ public function test_unauthenticated_user_cannot_access_product() { $response = $this->get('/products'); $response->assertStatus(302); $response->assertRedirect('login'); }}
The last test for authentication uses the POST method instead of GET.
tests/Feature/AuthTest.php:
use App\Models\User;use Illuminate\Foundation\Testing\RefreshDatabase; class AuthTest extends TestCase{ use RefreshDatabase; public function test_login_redirects_to_products() { User::create([ 'name' => 'User', 'email' => 'user@user.com', 'password' => bcrypt('password123') ]); $response = $this->post('/login', [ 'email' => 'user@user.com', 'password' => 'password123' ]); $response->assertStatus(302); $response->assertRedirect('products'); } // ...}
So, in short, if you want to test the authenticated user access, use actingAs()
with a specific User model value.