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

Avoid creating the same data: Private methods or setUp()

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.


First Method

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.


Second Method

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.

tests refactor user creation

When using setUp to add global variables, don't forget to add parent::setUp() first.