In this lesson, let's see how to avoid repeating some code in the tests. Now, we use a factory to create a user in every test. We will extract this part and see how to do it in two ways. The first is a general PHP way, and the second is setting up with PHPUnit and Laravel testing logic.
For the method, let's create a new private function where we will create a new user and return its Model.
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(); $user = $this->createUser(); $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(); $user = $this->createUser(); $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(); $user = $this->createUser(); $response = $this->actingAs($user)->get('/products'); $response->assertStatus(200); $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($lastProduct) { return $collection->doesntContain($lastProduct); }); } private function createUser(): User { return User::factory()->create(); } }
But the code didn't get much shorter, and this is more beneficial if you have more logic. For example, a few more lines of code related to the same user.
A better way to refactor this code would be to have some constructor. In tests, the constructor is in a method setUp
. In the setUp
, we can assign a user to a private property and call this property where we need it.
tests/Feature/ProductsTest.php:
use App\Models\User; class ProductsTest extends TestCase{ use RefreshDatabase; private User $user; protected function setUp(): void { parent::setUp(); $this->user = $this->createUser(); } public function test_homepage_contains_empty_table(): void { $user = $this->createUser(); $response = $this->actingAs($user)->get('/products'); $response = $this->actingAs($this->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 = $this->createUser(); $response = $this->actingAs($user)->get('/products'); $response = $this->actingAs($this->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 = $this->createUser(); $response = $this->actingAs($user)->get('/products'); $response = $this->actingAs($this->user)->get('/products'); $response->assertStatus(200); $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($lastProduct) { return $collection->doesntContain($lastProduct); }); } private function createUser(): User { return User::factory()->create(); }}
Let's re-run the tests to make sure everything works.
When using setUp
to add global variables, don't forget to add parent::setUp()
first.