Summer Sale! All accounts are 50% off this week.

lorenlang's avatar

Factory with no migration?

I'm wanting to use a factory to generate a model for testing I'm getting an empty model. Some digging and it looks like factories have to have a migration to work. Is this correct?

My application uses an existing database and I don't want or need a migration for some of the models I'm using. But I'd like to be able to test offline and not need to be dependent on the database. Additionally, I don't want to have to write tests based on existing data that could change.

Is there a way to have a model factory that simply returns a model with a bunch of faked up properties?

0 likes
6 replies
LaryAI's avatar
Level 58

Yes, it is possible to create a factory without a migration. You can define the factory in your test file and use the make method to create a new instance of the model with fake data. Here's an example:

use App\Models\User;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition()
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'email_verified_at' => now(),
            'password' => bcrypt('password'),
            'remember_token' => Str::random(10),
        ];
    }
}

// In your test file
$user = UserFactory::new()->make();

In this example, we define a UserFactory class that extends Laravel's built-in Factory class. We set the $model property to User::class so that Laravel knows which model to use. We then define a definition method that returns an array of fake data for the model.

In your test file, you can use the make method to create a new instance of the model with fake data. This will allow you to test your code without relying on the database.

Note that if you want to use the create method instead of make, you will need to have a migration for the model.

tykus's avatar

If you need an in-memory instance, then your test does not need the database; use make rather than create method:

$user = User::factory()->make([/* custom attributes */]);
Snapey's avatar

But you won't be able to test anything that persists data to the database.

tykus's avatar

@Snapey I supposed that was the OP's intent

But I'd like to be able to test offline and not need to be dependent on the database

Snapey's avatar

@tykus Which you would normally do with a testing database, probably using sqlite ?

lorenlang's avatar

I am using make() and I don't want to persist anything to that particular database. The problem is that I'm just getting an empty model back. It's the correct model (based on the defined connection, table, and primaryKey properties, but it has no attributes.

Do the properties have to exist in the model class? Since it's only for retrieving data, I basically have an empty model class and the ORM populates it nicely. I do have PhpUnit set to use sqlite all db connections whether I actually need them or not.

The model:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Employee extends Model
{
    use HasFactory;

    protected $connection;
    protected $table        = 'view_Employee';
    protected $primaryKey   = 'EmployeeNumber';
    protected $keyType      = 'string';
    public    $incrementing = FALSE;
    public    $timestamps   = FALSE;


    public function __construct()
    {
        $this->connection = config('aep.db_connection.gpdb');;
    }

}

and the factory:

namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Employee>
 */
class EmployeeFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        $lname = fake()->lastName;
        $fname = fake()->firstName;
        $mname = fake()->firstName;
        $title = fake()->title;

        return [
            'EmployeeNumber' => fake()->numberBetween(1001, 1999),
            'LastName'       => $lname,
            'FirstName'      => $fname,
            'MiddleName'     => $mname,
            'Nickname'       => '',
            'AltName'        => '',
            'Suffix'         => '',
            'Prefix'         => $title,
            'DisplayName'    => join(' ', array_filter([$title, $fname, $mname, $lname])),
            'SSN'            => fake()->randomNumber(9),
            'Gender'         => fake()->randomElement(['M', 'F']),
            'Email'          => fake()->email,
            'PersonalPhone'  => fake()->phoneNumber,
            'OtherPhone'     => fake()->phoneNumber,
            'OfficePhone'    => fake()->phoneNumber,
            'OfficeExt'      => fake()->randomNumber(4),
            'DOB'            => fake()->date,
            'Address1'       => fake()->streetAddress,
            'Address2'       => '',
            'Address3'       => '',
            'City'           => fake()->city,
            'State'          => '',
            'Zip'            => fake()->postcode,
            'Country'        => '',
            'JobDescription' => fake()->jobTitle,
            'Department'     => '',
            'OfficeLocation' => Str::random(2) . fake()->randomNumber(3),
            'DirPhone'       => fake()->boolean ? 1 : 0,
            'DirAddress'     => fake()->boolean ? 1 : 0,
        ];
    }
}

and the test (fails):

namespace Tests\Models;

use App\Models\Employee;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class EmployeeTest extends TestCase
{
    use RefreshDatabase;

    public function testHasAnEmployeeNumber()
    {
        $e = Employee::factory()->make();
        $this->assertObjectHasProperty('EmployeeNumber', $e);
    }
}

Please or to participate in this conversation.