ajmcgrail's avatar

Correct usage of factories

Hello all,

I'm not sure if I'm posting this in the correct place, but I'm looking for the correct way to deal with using factories in phpunit testing in regards to fillables. I'm on Laravel 11. Psuedo MVP below.

For example, let's say I have a Vulnerability model with a belongsTo relationship to an Asset model:

Vulnerability:

  • id
  • description (fillable)
  • asset_id (not nullable)

Asset: -id

If I were to make a factory definition for Vulnerability and I wanted to have an asset automatically be added using factory to a vulnerability, I would think I would follow the documentation and make a definition like:

public function definition(): array
{
	return [
		'description' => $this->faker->paragraph,
		'asset_id' => Asset::factory(),
	]
} 

However this doesn't seem to work with just Vulnerability::factory()->create(), and there'll be an error about a lack of default value for Vulnerability- presumably because asset_id isn't fillable. But I don't want asset_id to be fillable anyway 99% of the time, to prevent users from being able to set it outside of specific routes. In order to follow the method outlined in the documentation, I'd need to add asset_id to the list of fillables, which I'd very much prefer NOT to do. One would think you might be able to work around by doing Vulnerability::unguard() in the given test method where Vulnerability::factory()->create() is used, but as far as I can tell, this also doesn't work and I get the 'does not have default value' error. If the column is not inside of the $fillable array in the model, specifying the factory inside of the definition does not work.

All of the above is irrelevant when doing Vulnerability::factory()->make(), presumably because that never actually persists to the database.

Does anyone have suggestions on how to deal with the issue of fillable + using a factory for a non-fillable column? Any advice is appreciated.

P.S. This is my first post here. If I somehow broke forum etiquette, please let me know.

EDIT: Intermediate update in the event I forget the existence of this thread:

If recycle is used (as in you create an asset by doing $asset = Asset::factory()->create(), and do Vulnerability::factory()->recycle($asset), there's no 'default value missing' error and it all works correctly. It doesn't address the problem regarding creating a default Asset if one wasn't given to the factory, but it's at least a way to partially address the issue.

1 like
4 replies
tykus's avatar

Mass assignment protection is automatically disabled when creating models using factories.

Docs reference

Can you share the full error message you are seeing? Where and how are you using the Model Factory?

1 like
SilenceBringer's avatar

If you need to create just 1 instance - mergeFillable method will help you, but you'll need to make the model first, and thet save it.

Vulnerability::factory()
	->make()
	->mergeFillable(['asset_id'])
	->save()

or you can move mergeFillable method to the factory callback https://laravel.com/docs/12.x/eloquent-factories#factory-callbacks

Not sure how good it will be with mass factory creation, just as the first idea

1 like
vincent15000's avatar

There shouldn't be any problem regarding to mass assignment when you are using factories.

Have you defined a factory for the Asset model ? And have you declared use hasFactory at the beginning of the model class ?

martinbean's avatar

Does anyone have suggestions on how to deal with the issue of fillable + using a factory for a non-fillable column? Any advice is appreciated.

@ajmcgrail You’re describing a non-issue, because factories are ran with mass assignment protection disabled.

So, if you are getting an error, drop the “pseudo code” and post the actual error and code. Because I’ve lost count the amount of times someone’s edited their code, only to edit out the actual problem and make people waste time hunting for an issue that doesn’t actually exist in the edited code presented.

1 like

Please or to participate in this conversation.