In this lesson, let's see how we can easily use Eloquent factories to create fake data for tests easily.
Let's imagine we want to test pagination. Our table would contain ten records with pagination, and you want to test that pagination works. We will need to create eleven records and test that the last record isn't showing on the page.
First, in the Controller, we need to change to use pagination.
app/Http/Controllers/ProductController.php:
class ProductController extends Controller{ public function index(): View { $products = Product::all(); $products = Product::paginate(10); return view('products.index', compact('products')); }}
And the test.
tests/Feature/ProductsTest.php:
use App\Models\Product;use Illuminate\Pagination\LengthAwarePaginator;use Illuminate\Foundation\Testing\RefreshDatabase; class ProductsTest extends TestCase{ use RefreshDatabase; // ... public function test_homepage_contains_non_empty_table(): void { $product = Product::create([ 'name' => 'Product 1', 'price' => 123, ]); $response = $this->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() { for ($i = 1; $i <= 11; $i++) { $product = Product::create([ 'name' => 'Product ' . $i, 'price' => rand(100, 999), ]); } $response = $this->get('/products'); $response->assertStatus(200); $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($product) { return $collection->doesntContain($product); }); } }
We used the PHP function for
in the test to create eleven records. Then we go to /products
URL and assert that the collection doesn't contain the last product. Now, let's run the tests.
The tests are green, but creating the for loop isn't convenient. Here is where the Factories should be used.
You can find factories in a database/factories
directory. With Laravel comes one default Factory, UserFactory
. You specify the rules of different fields in the definition
method. You have a fake()
helper to fake some data because you don't care what that name is in the tests. What faker has formatters you can check in the official documentation.
database/factories/UserFactory.php:
class UserFactory extends Factory{ public function definition(): array { return [ 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } public function unverified(): static { return $this->state(fn (array $attributes) => [ 'email_verified_at' => null, ]); }}
Now, let's create a Factory for a Product
Model.
php artisan make:factory ProductFactory
And in the ProductFactory
, we need to provide rules for two fields.
database/factories/ProductFactory.php:
class ProductFactory extends Factory{ public function definition(): array { return [ 'name' => fake()->text(), 'price' => rand(100, 999), ]; }}
To use the Factory Model, it needs to have a HasFactory
trait.
app/Models/Product.php:
use Illuminate\Database\Eloquent\Factories\HasFactory; class Product extends Model{ use HasFactory; // ...}
Then, we can replace the for loop with a factory in the test.
tests/Feature/ProductsTest.php:
use App\Models\Product;use Illuminate\Foundation\Testing\RefreshDatabase; class ProductsTest extends TestCase{ use RefreshDatabase; // ... public function test_paginated_products_table_doesnt_contain_11th_record() { Product::factory(11)->create(); for ($i = 1; $i <= 11; $i++) { $product = Product::create([ 'name' => 'Product ' . $i, 'price' => rand(100, 999), ]); } $response = $this->get('/products'); $response->assertStatus(200); $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($product) { return $collection->doesntContain($product); }); }}
But now we don't have the $product
variable. Here, we can assign Factory to a variable, a collection, and use the last
method to get the last record.
tests/Feature/ProductsTest.php:
class ProductsTest extends TestCase{ use RefreshDatabase; // ... public function test_paginated_products_table_doesnt_contain_11th_record() { $products = Product::factory(11)->create(); $lastProduct = $products->last(); $response = $this->get('/products'); $response->assertStatus(200); $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($product) { $response->assertViewHas('products', function (LengthAwarePaginator $collection) use ($lastProduct) { return $collection->doesntContain($product); return $collection->doesntContain($lastProduct); }); }}
After relaunching the tests, everything should still be green.
Also, knowing that you can overwrite the values is very useful. In the create
, you can pass an array of parameters. For example, if you would want to hardcode the price.
tests/Feature/ProductsTest.php:
class ProductsTest extends TestCase{ use RefreshDatabase; // ... public function test_paginated_products_table_doesnt_contain_11th_record() { $products = Product::factory(11)->create([ 'price' => 9999, ]); // ... }}
All products will be created with this price instead of one set in the Factory.