vierge-noire/cakephp-fixture-factories
Fixture factories for CakePHP let you define reusable blueprints and generate test data with associations, defaults, and sequences. Build fixtures fluently, cut boilerplate, and keep integration tests readable and reliable.
Install it as a dev dependency and make sure your test database is migration-ready. Fixture factories do not generate fixture files; they write entities through the ORM into the test database, so the schema must already exist.
composer require --dev vierge-noire/cakephp-fixture-factories
Typical setup:
tests/bootstrap.php in CakePHP 4/5, so its helpers and test integration are available only in the test environment.vierge-noire/cakephp-test-suite-light if you want automatic database cleanup between tests. Without a cleanup strategy, persisted factory data will leak across tests.Create your first factory under tests/Factory/:
namespace App\Test\Factory;
use CakephpFixtureFactories\Factory\BaseFactory;
use Faker\Generator;
class UserFactory extends BaseFactory
{
protected function getRootTableRegistryName(): string
{
return 'Users';
}
protected function setDefaultTemplate(): void
{
$this->setDefaultData(function (Generator $faker) {
return [
'first_name' => $faker->firstName(),
'last_name' => $faker->lastName(),
'email' => $faker->unique()->safeEmail(),
];
});
}
}
First use case: create one entity in a test and assert against it.
public function testLogin(): void
{
$user = UserFactory::make([
'email' => '[email protected]',
'password' => 'secret',
])->persist();
$this->post('/login', ['email' => '[email protected]', 'password' => 'secret']);
$this->assertResponseSuccess();
}
Where to look first:
make / persist / association API for your installed version.BaseFactory class for the available hooks: default template, root table, association helpers, and data patching.tests/Factory directory once the first factory works; build outward from there.One factory per table, kept thin. Put only valid defaults in setDefaultTemplate(). Keep business-specific scenarios in named methods or in the test itself. This mirrors Laravel factories: defaults are boring and valid, overrides tell the story.
$user = UserFactory::make(['role' => 'admin'])->persist();
Create one vs many. Use single-entity creation for focused tests and plural creation for list/index/pagination tests. Check the exact return shape in your version; persisted collections usually come back as an array of entities.
$users = UserFactory::make(3)->persist();
Use make() for unsaved entities. If you are testing validation, entity rules, or pure domain logic, avoid touching the database. Build the entity, assert, and only call persist() when persistence itself is part of the behavior under test.
$user = UserFactory::make(['email' => 'not-an-email'])->make();
Declare associations explicitly. The biggest day-to-day pattern is composing factories. Define associations in the default template when every instance needs them, or at call time when only some tests do. Typical CakePHP relationships map cleanly to with/withMany-style helpers; use the package API rather than manually assigning foreign keys.
$article = ArticleFactory::make()
->with('Users', 1) // check exact helper name for your version
->persist();
For deterministic tests, it is often clearer to create the parent first and pass its key:
$user = UserFactory::make()->persist();
$article = ArticleFactory::make([
'user_id' => $user->id,
])->persist();
Add state helpers for repeatable scenarios. A small number of named methods beats copying override arrays across tests.
class UserFactory extends BaseFactory
{
public function admin(): static
{
return $this->patchData(['role' => 'admin']);
}
public function inactive(): static
{
return $this->patchData(['active' => false]);
}
}
Then: UserFactory::make()->admin()->persist();
Mix factories with fixtures deliberately. Factories are excellent for domain data that varies per test: users, orders, articles. Traditional fixtures are still convenient for static reference data: countries, currencies, permission matrices. Many teams use both: fixtures for lookup tables, factories for everything test-specific.
Structure tests around arrange/act/assert. Factories belong in arrange. Avoid persisting inside assertions or helpers that hide multiple writes. If a test needs five entities from four tables, make that arrangement visible so failures are diagnosable.
Use Faker thoughtfully. Keep Faker values valid for the column type and length. For unique columns, use unique() or a sequence rather than random values. For tests that assert exact values, override the Faker default instead of relying on generated output.
Reuse a base factory for shared columns. If several factories share audit fields or tenant IDs, extract a small base class or trait. Do not over-abstract; factories are cheap and readable duplication is often better than a deep hierarchy.
getRootTableRegistryName() expects the alias your application uses, not necessarily the class name. A typo here surfaces as a confusing “table not found” or association error.$faker->email() can repeat across a large test. Use unique() or a counter when the column has a unique index. The same applies to slugs and usernames.Time/DateTime handling can disagree in assertions. Override date fields with explicit values when the test asserts on them.make() without persist(), dump the entity, and confirm field names and association keys. Then persist. This separates “factory produced bad data” from “database rejected good data.”with, has, or persistence return values.How can I help you explore Laravel packages today?