Skip to content
Kona

Seeders & factories

A fresh database is empty. Seeders put data into it: reference data your application needs (countries, roles) or realistic sample data for development. Factories build model instances from a template of default attributes, so tests and seeders can create “a user” or “ten posts” in one line.

A factory extends org.konaframework.cadence.factory.Factory<M> and implements definition(), which returns the default attributes for one model. The protected index property counts the models this factory has built (starting at 0); use it to make values unique.

package database.factories
import app.Models.User
import org.konaframework.cadence.factory.Factory
class UserFactory : Factory<User>(User::class.java) {
override fun definition(): Map<String, Any?> = mapOf(
"name" to "User $index",
"email" to "user$index@example.com",
"admin" to false,
)
}

./grind make:factory UserFactory (or ./grind make:model User --factory) writes this skeleton under database/factories/.

val user = UserFactory().make() // unsaved model
val saved = UserFactory().create() // saved model
val admin = UserFactory().create(mapOf("admin" to true)) // override attributes
val many = UserFactory().count(10).createMany() // 10 saved models
val drafts = UserFactory().count(3).makeMany() // 3 unsaved models
val attributes = UserFactory().raw() // just the map

Factories set attributes with forceFill, so fillable and guarded do not apply: the factory is trusted code.

The builder methods change the factory and return it, so you can chain them. They apply in this order: definition(), then each state in the order added, then the current sequence entry, then the overrides passed to make / create.

UserFactory()
.state(mapOf("admin" to true)) // fixed overrides
.state { attrs -> attrs + ("email" to attrs["email"].toString().uppercase()) } // computed
.sequence(mapOf("team" to "red"), mapOf("team" to "blue")) // alternates per model
.afterMaking { user -> user["nickname"] = user["name"] }
.afterCreating { user -> user.posts().create(mapOf("title" to "Welcome")) }
.count(4)
.createMany()

Because builder calls change the factory, create a new factory for an unrelated batch rather than reusing a configured one.

An attribute value may be:

  • a java.util.function.Supplier, called once per model;
  • another Factory, whose model is created and whose key is used. This is how you give each post its own author:
class PostFactory : Factory<Post>(Post::class.java) {
override fun definition(): Map<String, Any?> = mapOf(
"title" to "Post $index",
"user_id" to UserFactory(), // a new user per post
"slug" to Supplier { "post-${System.nanoTime()}" },
)
}

Pass an explicit key in the overrides to reuse an existing model: PostFactory().count(3).createMany(mapOf("user_id" to ada.getKey())).

Factory.of builds a factory from a lambda, which is handy in Java tests:

Factory<User> users = Factory.of(User.class, () -> Map.of("name", "Ada", "email", "ada@example.com"));
users.count(2).createMany();

A seeder extends org.konaframework.cadence.seeding.Seeder and implements run(). A root seeder, conventionally database.seeders.DatabaseSeeder, calls the others with call so they run in a known order.

package database.seeders
import database.factories.UserFactory
import org.konaframework.cadence.DB
import org.konaframework.cadence.seeding.Seeder
class RoleSeeder : Seeder() {
override fun run() {
DB.table("roles").insert(listOf(mapOf("name" to "admin"), mapOf("name" to "member")))
}
}
class UserSeeder : Seeder() {
override fun run() {
UserFactory().count(20).createMany()
}
}
class DatabaseSeeder : Seeder() {
override fun run() {
call(RoleSeeder::class.java, UserSeeder::class.java)
}
}
  • call(SomeSeeder::class.java, …) builds each seeder through the container (the application’s container when run by grind, or a fresh one), so seeder constructors can ask for services. It then runs the seeders in order.
  • call(SomeSeeder(), …) runs instances you created yourself.
  • called lists the simple names of every seeder run through call, nested calls included.
  • Seeder.instantiate(type, container) builds a seeder the same way call does, for running one from your own code.

Seeders and the factories they use write through the global database, so the database must be registered (with CadenceServiceProvider or Database.setAsGlobal()) before they run.

Terminal window
./grind make:factory UserFactory # database/factories/UserFactory
./grind make:factory CrewFactory --model=Person # factory for app.Models.Person
./grind make:seeder UserSeeder # database/seeders/UserSeeder
./grind make:model User --factory --seeder # model plus both
./grind make:model User --all # model, migration, factory, seeder
./grind db:seed # runs database.seeders.DatabaseSeeder
./grind db:seed --class=database.seeders.UserSeeder

db:seed boots the application in-process from its compiled classes (run ./gradlew classes first), makes its database global, resolves the seeder through the application container, runs it, and prints each seeder it ran. It fails with a message when the class is missing or does not extend Seeder.

New applications created by kona new compile the database/ directory along with app/, so generated migrations, factories, and seeders are part of the build.