{"id":50376816,"url":"https://github.com/sya-ri/fiktion","last_synced_at":"2026-05-30T10:00:45.173Z","repository":{"id":361014278,"uuid":"1251571997","full_name":"sya-ri/fiktion","owner":"sya-ri","description":"Kotlin fake data for tests that should read like intent, not fixture setup.","archived":false,"fork":false,"pushed_at":"2026-05-28T20:08:41.000Z","size":611,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-28T22:05:05.739Z","etag":null,"topics":["kotlin","kotlin-compiler","kotlin-compiler-plugin","kotlin-js","kotlin-library","kotlin-multiplatform","kotlin-native","kotlin-test","kotlin-testing","test","testing"],"latest_commit_sha":null,"homepage":"http://fiktion.s7a.dev","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sya-ri.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"ko_fi":"sya_ri"}},"created_at":"2026-05-27T17:56:17.000Z","updated_at":"2026-05-28T20:08:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sya-ri/fiktion","commit_stats":null,"previous_names":["sya-ri/fiktion"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/sya-ri/fiktion","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sya-ri%2Ffiktion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sya-ri%2Ffiktion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sya-ri%2Ffiktion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sya-ri%2Ffiktion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sya-ri","download_url":"https://codeload.github.com/sya-ri/fiktion/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sya-ri%2Ffiktion/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33687722,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["kotlin","kotlin-compiler","kotlin-compiler-plugin","kotlin-js","kotlin-library","kotlin-multiplatform","kotlin-native","kotlin-test","kotlin-testing","test","testing"],"created_at":"2026-05-30T10:00:33.340Z","updated_at":"2026-05-30T10:00:45.168Z","avatar_url":"https://github.com/sya-ri.png","language":"Kotlin","funding_links":["https://ko-fi.com/sya_ri"],"categories":[],"sub_categories":[],"readme":"# Fiktion\n\n[![CI](https://github.com/sya-ri/fiktion/actions/workflows/ci.yml/badge.svg)](https://github.com/sya-ri/fiktion/actions/workflows/ci.yml)\n[![Maven Central](https://img.shields.io/maven-central/v/dev.s7a/fiktion-core?label=maven%20central)](https://central.sonatype.com/artifact/dev.s7a/fiktion-core)\n[![Gradle Plugin Portal](https://img.shields.io/gradle-plugin-portal/v/dev.s7a.fiktion?label=gradle%20plugin)](https://plugins.gradle.org/plugin/dev.s7a.fiktion)\n\nFiktion is a Kotlin Multiplatform fake data library for tests that should read like intent, not fixture setup.\n\nIt creates complete, realistic-enough object graphs from your Kotlin types, then lets each test override only the values\nthat matter. No annotations in production code. No builders for every model. No fixture files drifting away from the\ndomain.\n\n```kotlin\n@Test\nfun `paid orders are shipped`() {\n    val order = fake\u003cOrder\u003e {\n        Order::status generates OrderStatus.Paid\n        // Other values are filled in automatically.\n    }\n\n    shippingService.ship(order)\n\n    assertEquals(\n        ShipmentStatus.Created,\n        shipmentRepository.findByOrderId(order.id).status\n    )\n}\n```\n\nFiktion is built for the common testing problem where the object shape matters, but most individual values do not:\n\n```kotlin\nval user = fake\u003cUser\u003e {\n    User::id generates \"user-1\"\n}\n```\n\nYou get:\n\n- object graph generation for ordinary Kotlin classes\n- scoped overrides for the fields a test actually asserts\n- deterministic output with seeds\n- collection, map, nullable, default-value, enum, sealed, object, class, data class, and value class support\n- add-ons for external libraries, including common Java/JVM standard library types\n\n## Status\n\nFiktion is pre-release. The core behavior is usable, but API names and compiler-generated metadata internals may change\nbefore 1.0.\n\nThe repository currently contains:\n\n- `fiktion-core`: runtime APIs and built-in generators\n- `fiktion-compiler-plugin`: Kotlin compiler plugin for generated type metadata\n- `fiktion-gradle-plugin`: Gradle wiring for test source sets\n- `fiktion-addon-java`: rules for common Java/JVM standard library types\n\n## Install\n\nApply the Gradle plugin and add the runtime to your test dependencies:\n\n```kotlin\nplugins {\n    kotlin(\"jvm\") version \"2.3.21\"\n    id(\"dev.s7a.fiktion\") version \"0.2.3\"\n}\n\ndependencies {\n    testImplementation(\"dev.s7a:fiktion-core:0.2.3\")\n\n    // Optional: common JVM types such as Instant, UUID, URI, and Java collections.\n    testImplementation(\"dev.s7a:fiktion-addon-java:0.2.3\")\n}\n```\n\nFiktion is enabled for test source sets by default, including JVM `test` and Multiplatform source sets such as\n`commonTest` and `jvmTest`.\n\n## Basic Usage\n\nGenerate a value with `fake\u003cT\u003e()`:\n\n```kotlin\nval text = fake\u003cString\u003e()\nval count = fake\u003cInt\u003e()\nval user = fake\u003cUser\u003e()\n```\n\nUse a seed when a test needs repeatable data:\n\n```kotlin\nval first = fake\u003cUser\u003e(seed = 123)\nval second = fake\u003cUser\u003e(seed = 123)\n\ncheck(first == second)\n```\n\nOverride only what the test cares about:\n\n```kotlin\nval user = fake\u003cUser\u003e {\n    User::id generates \"user-1\"\n    User::displayName generates \"Test User\"\n}\n```\n\nRules inside `fake\u003cT\u003e { ... }` apply to that generated object graph only, so one test does not accidentally configure\nanother model.\n\n## Nested Properties\n\nNested objects can be configured inline:\n\n```kotlin\nval user = fake\u003cUser\u003e {\n    User::profile {\n        Profile::nickname generates \"example\"\n    }\n}\n```\n\nYou can also target a nested path directly:\n\n```kotlin\nval user = fake\u003cUser\u003e {\n    (User::profile / Profile::nickname) generates \"example\"\n}\n```\n\nName rules are useful when the same convention appears across several types:\n\n```kotlin\nval user = fake\u003cUser\u003e {\n    name(\"id\") generates \"user-1\"\n    name(\".*Name\".toRegex()) generatesBy { \"generated-name\" }\n}\n```\n\n## Collections And Maps\n\nCollections can generate each element automatically:\n\n```kotlin\nval catalog = fake\u003cCatalog\u003e {\n    Catalog::items generates auto withSize 3\n}\n```\n\nOr you can provide element rules:\n\n```kotlin\nval catalog = fake\u003cCatalog\u003e {\n    Catalog::tags generatesEach {\n        string(length = 8)\n    } withSize (1..5)\n}\n```\n\nMaps support key, value, and entry generation:\n\n```kotlin\nval index = fake\u003cSearchIndex\u003e {\n    SearchIndex::entries generatesKeys { string(length = 8) }\n    SearchIndex::entries generatesValues { fake\u003cEntry\u003e() }\n\n    SearchIndex::aliases generatesOneOf listOf(\"primary\", \"secondary\")\n}\n```\n\n## Nulls And Defaults\n\nNullable values can be generated by probability:\n\n```kotlin\nval user = fake\u003cUser\u003e {\n    User::nickname generates \"nickname\" orNullAt 0.3\n}\n```\n\nConstructor defaults can also be selected when Fiktion has metadata for the type:\n\n```kotlin\nval user = fake\u003cUser\u003e {\n    User::profile generates Profile(nickname = \"generated\") orDefaultAt 30.percent\n}\n```\n\n`Double` probabilities use `0.0..1.0`. Percentage helpers are available with `percent`.\n\nWhen a name rule intentionally generates `null`, declare the value type explicitly:\n\n```kotlin\nname\u003cString?\u003e(\"nickname\") generates null\nname\u003cString?\u003e(\"nickname\") generatesBy { null }\n```\n\n## Reusable Configuration\n\nUse `Fiktion { ... }` when a test suite needs a local generator configuration:\n\n```kotlin\nval fiktion = Fiktion {\n    type\u003cUser\u003e() generatesBy {\n        User(id = \"api-user-${random.nextLong()}\")\n    }\n}\n\nval user = fiktion.fake\u003cUser\u003e()\n```\n\nUse `Fiktion.configure` for process-wide test configuration. It returns a snapshot so the previous configuration can be\nrestored:\n\n```kotlin\nval snapshot = Fiktion.configure {\n    name\u003cString\u003e(\"email\") generatesBy {\n        \"test-${random.nextInt()}@example.test\"\n    }\n}\n\ntry {\n    val user = fake\u003cUser\u003e()\n} finally {\n    check(snapshot.restore())\n}\n```\n\n`Fiktion.configure` is useful when the same defaults should apply to top-level `fake\u003cT\u003e()` calls across a test project\nor framework-managed test context:\n\n```kotlin\nval snapshot = Fiktion.configure {\n    name\u003cString\u003e(\"email\") generatesBy {\n        \"user-${random.nextLong()}@example.test\"\n    }\n    name\u003cString\u003e(\"id\") generatesBy {\n        \"id-${random.nextLong()}\"\n    }\n}\n```\n\nBecause the configuration is global, prefer `fake\u003cT\u003e { ... }` or `Fiktion { ... }` for rules that only one test needs.\nWhen global configuration is installed by a test hook, restore the snapshot in the matching teardown hook. `restore()`\nreturns `false` if another `Fiktion.configure` call has installed a newer global configuration; use `restore(force =\ntrue)` only from cleanup code that owns the whole test process or project-level configuration.\n\nRule precedence is:\n\n1. Per-call rules\n2. Fiktion instance rules\n3. Global rules\n4. Add-on rules\n5. Built-in rules\n\nWithin the same precedence level, more specific targets win before registration order. When two matching rules have the\nsame specificity, the later registration wins.\n\n## Test Framework Integration\n\nWith `kotlin.test`, prefer an isolated `Fiktion { ... }` instance from `@BeforeTest` when each test should start from\nthe same defaults. Use `Fiktion.configure` with `@BeforeTest` / `@AfterTest` only when the test intentionally needs\ntop-level `fake\u003cT\u003e()` calls to see global rules:\n\n```kotlin\nimport dev.s7a.fiktion.Fiktion\nimport dev.s7a.fiktion.fake\nimport kotlin.test.BeforeTest\nimport kotlin.test.Test\n\nclass UserServiceTest {\n    private lateinit var fiktion: Fiktion\n\n    @BeforeTest\n    fun configureFiktion() {\n        fiktion = Fiktion {\n            name\u003cString\u003e(\"email\") generatesBy {\n                \"user-${random.nextLong()}@example.test\"\n            }\n        }\n    }\n\n    @Test\n    fun `creates a user`() {\n        val user = fiktion.fake\u003cUser\u003e()\n\n        // test body\n    }\n}\n```\n\nWith JUnit 5, `@TestInstance(TestInstance.Lifecycle.PER_CLASS)` only shares one test class instance across methods in\nthat class. Prefer an isolated instance for class-scoped defaults:\n\n```kotlin\nimport dev.s7a.fiktion.Fiktion\nimport dev.s7a.fiktion.fake\nimport dev.s7a.fiktion.generators.string\nimport org.junit.jupiter.api.BeforeAll\nimport org.junit.jupiter.api.Test\nimport org.junit.jupiter.api.TestInstance\n\n@TestInstance(TestInstance.Lifecycle.PER_CLASS)\nclass UserRepositoryTest {\n    private lateinit var fiktion: Fiktion\n\n    @BeforeAll\n    fun configureFiktion() {\n        fiktion = Fiktion {\n            name\u003cString\u003e(\"email\") generatesBy {\n                \"${string(length = 12)}@example.test\"\n            }\n        }\n    }\n\n    @Test\n    fun `finds user by email`() {\n        val user = fiktion.fake\u003cUser\u003e()\n\n        // test body\n    }\n}\n```\n\nUse `Fiktion.configure` when the rules intentionally need to affect global `fake\u003cT\u003e()` calls, for example project-wide\ntest helpers or framework hooks that do not receive a `Fiktion` instance. Since this mutates process-wide state, keep it\nat project-level setup when tests may run in parallel.\n\nFor JUnit 5, put process-wide defaults in an extension and store the snapshot in the root extension store:\n\n```kotlin\nimport dev.s7a.fiktion.Fiktion\nimport dev.s7a.fiktion.FiktionSnapshot\nimport org.junit.jupiter.api.extension.BeforeAllCallback\nimport org.junit.jupiter.api.extension.ExtensionContext\n\nclass FiktionExtension : BeforeAllCallback {\n    override fun beforeAll(context: ExtensionContext) {\n        context.root\n            .getStore(ExtensionContext.Namespace.GLOBAL)\n            .getOrComputeIfAbsent(FiktionResource::class.java) {\n                FiktionResource()\n            }\n    }\n\n    private class FiktionResource : AutoCloseable {\n        private val snapshot: FiktionSnapshot =\n            Fiktion.configure {\n                name\u003cString\u003e(\"email\") generatesBy {\n                    \"user-${random.nextLong()}@example.test\"\n                }\n            }\n\n        override fun close() {\n            check(snapshot.restore(force = true))\n        }\n    }\n}\n```\n\nWith Kotest, prefer an isolated instance for spec-scoped defaults. Kotest specs are `SingleInstance` by default, so a\nproperty initialized in the spec body is shared by tests in that spec:\n\nThe same pattern works with other Kotest spec styles such as `FunSpec`, `DescribeSpec`, `FreeSpec`, `ShouldSpec`, and\n`BehaviorSpec`; keep the `Fiktion { ... }` instance at the spec scope and call `fiktion.fake\u003cT\u003e()` from tests.\n\n```kotlin\nimport dev.s7a.fiktion.Fiktion\nimport dev.s7a.fiktion.fake\nimport io.kotest.core.spec.style.StringSpec\n\nclass UserRepositorySpec : StringSpec({\n    val fiktion = Fiktion {\n        name\u003cString\u003e(\"email\") generatesBy {\n            \"user-${random.nextLong()}@example.test\"\n        }\n    }\n\n    \"finds user by email\" {\n        val user = fiktion.fake\u003cUser\u003e()\n\n        // test body\n    }\n})\n```\n\nKotest project configuration is useful only when global defaults should affect top-level `fake\u003cT\u003e()` across the whole\ntest project:\n\n```kotlin\npackage io.kotest.provided\n\nimport dev.s7a.fiktion.Fiktion\nimport dev.s7a.fiktion.FiktionSnapshot\nimport io.kotest.core.config.AbstractProjectConfig\n\nclass ProjectConfig : AbstractProjectConfig() {\n    private lateinit var fiktionSnapshot: FiktionSnapshot\n\n    override suspend fun beforeProject() {\n        fiktionSnapshot = Fiktion.configure {\n            name\u003cString\u003e(\"email\") generatesBy {\n                \"user-${random.nextLong()}@example.test\"\n            }\n        }\n    }\n\n    override suspend fun afterProject() {\n        check(fiktionSnapshot.restore(force = true))\n    }\n}\n```\n\n## Rule Targets\n\nConfiguration scopes support type, property, path, and name targets:\n\n```kotlin\ntype\u003cUser\u003e() generatesBy { /* any User */ }\nproperty\u003cUser, String\u003e() generatesBy { /* any String property owned by User */ }\nproperty\u003cUser, String\u003e(\"id\") generates \"user-1\"\nname\u003cString\u003e(\"id\") generates \"shared-id\"\nname(\"email\") generatesBy { \"test-${random.nextInt()}@example.test\" }\nproperty(User::profile / Profile::nickname) generates \"example\"\nUser::id generates \"user-1\"\n```\n\nProperty references such as `User::id generates \"user-1\"` are the concise form for owner-specific property rules.\n`property\u003cUser, String\u003e(\"id\")` is the equivalent explicit form when the owner and value type should be spelled out.\n\n## Compiler Plugin\n\nThe compiler plugin generates runtime metadata for Kotlin types in enabled source sets. This is what lets Fiktion create\nobjects without annotations.\n\nSupported shapes include:\n\n- regular classes and data classes with supported primary constructors\n- value classes with one constructor value\n- enum classes\n- sealed classes and sealed interfaces\n- singleton objects and companion objects\n\nShapes that should be configured explicitly are skipped:\n\n- abstract classes and interfaces\n- fun interfaces and annotation classes\n- inner classes and local classes\n- classes without a primary constructor\n- private or protected primary constructors\n- vararg or otherwise unsupported constructor parameters\n\nValue class overrides depend on what the test wants to control. For a public underlying property, target that property:\n\n```kotlin\n@JvmInline\nvalue class UserId(val value: String)\n\nval userId = fake\u003cUserId\u003e {\n    UserId::value generates \"user-1\"\n}\n```\n\nFor a private underlying property, use a name rule because Kotlin code outside the class cannot reference the property:\n\n```kotlin\n@JvmInline\nvalue class UserId(private val value: String)\n\nval userId = fake\u003cUserId\u003e {\n    name(\"value\") generates \"user-1\"\n}\n```\n\nWhen the whole value object should be fixed, prefer a type rule in shared configuration:\n\n```kotlin\nval fiktion = Fiktion {\n    type\u003cUserId\u003e() generates UserId(\"user-1\")\n}\n```\n\nSkipped types can still be generated with explicit rules:\n\n```kotlin\nval fiktion = Fiktion {\n    type\u003cPrivateUser\u003e() generates PrivateUser.create(\"user-1\")\n}\n```\n\n## Gradle Configuration\n\nTest source sets are enabled by default. Main/runtime source sets are opt-in:\n\n```kotlin\nfiktion {\n    sourceSet(\"commonMain\") {\n        enabled.set(true)\n    }\n}\n```\n\nProject-wide controls are also available:\n\n```kotlin\nfiktion {\n    // Enable every Kotlin source set.\n    enabled.set(true)\n\n    // Disable the default test-source-set behavior.\n    testEnabled.set(false)\n\n    sourceSet(\"jvmTest\") {\n        enabled.set(true)\n    }\n}\n```\n\n## Java Add-On\n\nAdd `fiktion-addon-java` when tests need common JVM types such as `java.time`, `java.util`, `java.net`, `java.nio`,\n`java.sql`, or `java.util.concurrent` types:\n\n```kotlin\ndependencies {\n    testImplementation(\"dev.s7a:fiktion-addon-java:0.2.3\")\n}\n```\n\nWith the Gradle plugin enabled, add-ons on the compilation classpath are registered automatically before `fake\u003cT\u003e()`\ncalls:\n\n```kotlin\nimport java.time.Instant\nimport java.util.UUID\n\nval instant = fake\u003cInstant\u003e()\nval uuid = fake\u003cUUID\u003e()\n```\n\nAdd-ons can still be installed explicitly when the compiler plugin is not enabled for that source set:\n\n```kotlin\nval fiktion = Fiktion {\n    install(JavaFiktionAddon)\n}\n```\n\n## Custom Add-Ons\n\nAdd-ons are reusable bundles of rules:\n\n```kotlin\npublic object CustomFiktionAddon : FiktionAddon {\n    override val id: String = \"custom\"\n\n    override fun install(builder: FiktionAddonBuilder) {\n        with(builder) {\n            type\u003cToken\u003e() generatesBy {\n                Token(value = string(length = 32))\n            }\n        }\n    }\n}\n```\n\nTo make a third-party add-on auto-registerable, include a resource file named `META-INF/fiktion/addons` in the add-on\nartifact. Each non-empty line should contain one add-on object class name:\n\n```text\ncom.example.fiktion.ExampleFiktionAddon\n```\n\nInstalled add-ons sit below explicit per-call, instance, and global rules in precedence.\n\n## Realistic Data\n\nFiktion core focuses on object graph generation, rule resolution, and Kotlin metadata. Strict domain data such as names,\nemail addresses, postal addresses, or localized text can come from custom rules, add-ons, or libraries such as Datafaker.\n\n```kotlin\nval faker = Faker()\n\nval user = fake\u003cUser\u003e {\n    User::email generatesBy {\n        faker.internet().emailAddress()\n    }\n}\n```\n\n## Development\n\nLocal `./gradlew build` skips browser test execution unless `-Pfiktion.enableBrowserTests=true` is provided. CI enables\nbrowser tests and installs Chrome before running Gradle.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsya-ri%2Ffiktion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsya-ri%2Ffiktion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsya-ri%2Ffiktion/lists"}