{"id":13608665,"url":"https://github.com/bluegroundltd/kfactory","last_synced_at":"2026-01-17T16:28:19.663Z","repository":{"id":57732635,"uuid":"417546330","full_name":"bluegroundltd/kfactory","owner":"bluegroundltd","description":"Fixture factory in Kotlin","archived":false,"fork":false,"pushed_at":"2021-10-17T18:58:58.000Z","size":322,"stargazers_count":58,"open_issues_count":0,"forks_count":0,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-12T17:41:46.820Z","etag":null,"topics":["fixtures","jvm","kotlin"],"latest_commit_sha":null,"homepage":"","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/bluegroundltd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-15T15:20:43.000Z","updated_at":"2024-06-13T09:45:54.000Z","dependencies_parsed_at":"2022-09-13T15:00:31.294Z","dependency_job_id":null,"html_url":"https://github.com/bluegroundltd/kfactory","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bluegroundltd/kfactory","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluegroundltd%2Fkfactory","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluegroundltd%2Fkfactory/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluegroundltd%2Fkfactory/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluegroundltd%2Fkfactory/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bluegroundltd","download_url":"https://codeload.github.com/bluegroundltd/kfactory/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bluegroundltd%2Fkfactory/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28511864,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T13:38:16.342Z","status":"ssl_error","status_checked_at":"2026-01-17T13:37:44.060Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["fixtures","jvm","kotlin"],"created_at":"2024-08-01T19:01:29.032Z","updated_at":"2026-01-17T16:28:19.633Z","avatar_url":"https://github.com/bluegroundltd.png","language":"Kotlin","funding_links":[],"categories":["Kotlin","测试"],"sub_categories":["语音合成"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg width=\"240\" height=\"240\" src=\"https://raw.githubusercontent.com/bluegroundltd/kfactory/master/logo.png\" /\u003e\n\u003c/p\u003e\n\n# KFactory\n\n[![Build](https://github.com/bluegroundltd/kfactory/actions/workflows/ci_test.yaml/badge.svg?branch=main)](https://github.com/bluegroundltd/kfactory/actions/workflows/ci_test.yaml)\n\nCreate best-in-class factories for your synthetic data in Kotlin.\n\n⭐ \u0026nbsp; Test fixtures\u003cbr/\u003e\n⭐ \u0026nbsp; DB seeding\u003cbr/\u003e\n⭐ \u0026nbsp; Feature demos\u003cbr/\u003e\n⭐ \u0026nbsp; Pre-production environments\u003cbr/\u003e\n\n\n\u003chr/\u003e\n\n- [About KFactory](#about-kfactory)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Creating a Factory](#creating-a-new-factory)\n  - [Introducing Traits](#introducing-factory-traits)\n  - [Enhancing a Factory](#enhancing-a-factory-with-traits)\n  - [Producing objects](#producing-objects-from-a-factory)\n  - [Dynamic values](#generating-dynamic-values-every-time)\n\n\n## About KFactory\n\n### Why synthetic data?\n\nChances are that synthetic data are going to be a major painpoint\nfor your project sooner than later.\n\n* Local development\n* Unit tests\n* Exploratory testing\n* Showcase a feature\n* Pre-production environments\n\n_Are you going to need synthetic data for any of the above?_\n\n`Yes!` - Probably for all of them\n\n### Synthetic data strategies\n\nThere are 3 major ways to create synthetic data that we are aware of:\n\n1. Static fixtures\n2. Factories\n3. Production copies\n\n💙 \u0026nbsp;**Static fixtures** usually live in plain YAML or JSON files.\nThey are typically fed directly to the underlying database, skipping standard\nvalidation/integrity checks for simplicity and performance. Since data integrity\nis not a priority certain things like exploratory testing and refactorings become much harder.\nFinally, due to their hardcoded nature, generating large volumes of data is out of question.\nThat's why we typically recommend static fixtures only for small apps.\n\n💚 \u0026nbsp;**Production copies** provide a great way to populate a system with data.\nYou typically get valid, large and versatile enough data to cover most of use cases.\nBUT if you think about it... usually production data contains sensitive data and you'll need some\nsort of obfuscation/anonymization before you load them into a develoment/test system.\nThat's :exploding_head: - especially when there are multiple databases involved.\n\nSome other times, **production copies** can be too small - during the early days of a product - or too\nlarge to be of practical use. And certainly, you cannot really write unit or functional tests\non production copies cause they are dynamic and unpredictable. We typically recommend production\ncopies for populating pre-production environments.\n\n❤️ \u0026nbsp;**Factories** or dynamic fixtures if you like, directly produce models\nfrom within our domain. Models that can be validated and stored in the\nunderlying database with all integrity checks in place. A well made\nset of factories, is one that captures all the necessary abstractions that\nlet you create data for a certain business scenario in a few lines of code.\nWe typically recommend factories for most use cases, but primarily for\ncreating test fixtures and populating local development environments.\n\n\n### Why KFactory?\n\nOther ecosystems have robust synthetic data solutions for some time now,\nmostly inspired from Ruby's amazing [FactoryBot](https://github.com/thoughtbot/factory_bot).\n\nKFactory is also inspired by FactoryBot - in a Kotlin idiomatic way.\n\n* Built-in helpers\n* Composable factories\n* Traits\n* Lazy sequence builds\n\n\u003cbr/\u003e\n\n## Installation\n\nKFactory is published on `mavenCentral`. In order to use it just add the following dependency:\n\n```gradle\nimplementation(\"io.github.bluegroundltd:kfactory:1.0.0\")\n```\n\n## Usage\n\n* [API reference](https://bluegroundltd.github.io/kfactory/)\n* Check the [examples](https://github.com/bluegroundltd/kfactory/tree/main/examples) for some common use cases\n\n### Creating a new Factory\n\nWhen you have a domain entity that you want to create a `Factory` for you can start\nby doing the following:\n\n```kotlin\nclass AddressFactory : Factory\u003cAddress\u003e {\n  override fun produce() : Address = Address()\n}\n```\n\n### Introducing Factory traits\n\nA lot of times we want to produce fixtures from factories, but we only need to change only\na few of their attributes/characteristics.\n\nFor example:\n```kotlin\nclass AddressFactory(\n  private var city: String = \"city\",\n  private var state: String = \"state\",\n) : Factory\u003cAddress\u003e {\n\n  fun withCity(city: String) = apply {\n    this.city = city\n  }\n\n  fun withState(state: String) = apply {\n    this.state = state\n  }\n\n  override fun produce() : Address = Address(\n    city = city,\n    state = state\n  )\n}\n```\n\nNow if we want to produce several instances of `Address` that will retain `city`\nbut will have another specific value for `state`, we can create a new `FactoryTrait`\nthat we will later apply to that `Factory`.\n\n```kotlin\nobject CaliforniaTrait : FactoryTrait\u003cAddressFactory\u003e {\n  override fun modifyWithTrait(factory: AddressFactory): AddressFactory = factory\n    .withState(state = \"California\")\n}\n```\n\n### Enhancing a Factory with Traits\n\nIn order to enhance our `Factory` with a `FactoryTrait` like we previously saw,\nwe need to use the `TraitEnhancedFactory` marker interface.\n\nFor example consider the following:\n\n```kotlin\nclass AddressFactory(\n  private var city: String = \"city\",\n  private var state: String = \"state\",\n) : Factory\u003cAddress\u003e, TraitEnhancedFactory {\n\n  fun withCity(city: String) = apply {\n    this.city = city\n  }\n\n  fun withState(state: String) = apply {\n    this.state = state\n  }\n\n  override fun produce() : Address = Address(\n    city = city,\n    state = state\n  )\n}\n```\n\nThis immediately adds two new extension functions on our `Factory`:\n```kotlin\nfun withTraits(vararg traits: FactoryTrait)\n\nfun withTrait(trait: FactoryTrait)\n```\n\nWe can now start building factories with distinctive characteristics:\n```kotlin\nval californiaFactory: AddressFactory = AddressFactory()\n  .withTraits(CaliforniaTrait)\n\n```\n\n### Producing objects from a Factory\n\nAs described above we utilize factories in order to produce fixture data.\n\nThis can be done by invoking the following function on a `Factory`:\n\n```kotlin\nval address: Address = californiaFactory.produce()\n```\n\nIf we need to generate more than on instance of our fixture data, we can utilize the\nfollowing function of a `Factory` that returns a `Sequence` of objects:\n\n```kotlin\nval addresses: List\u003cAddress\u003e = californiaFactory.produceMany()\n  .take(5)\n  .toList()\n```\n\n### Generating dynamic values every time\n\nMost of the time in our fixture data we might need to produce random values, or have\na new value generated every time we invoke `.produce()` on one of our factories.\n\nFor that purpose, we include a `typealias` in our library, named `Yielded` and our\nproposed usage is the following:\n\n```kotlin\nclass AddressFactory(\n  private var city: String = \"city\",\n  private var state: String = \"state\",\n  private var streetNum: Yielded\u003cInt\u003e = { Random.nextint(1,5) }\n) : Factory\u003cAddress\u003e, TraitEnhancedFactory {\n\n  fun withCity(city: String) = apply {\n    this.city = city\n  }\n\n  fun withState(state: String) = apply {\n    this.state = state\n  }\n\n  fun withStreetNum(streetNum: Int) = apply {\n    this.streetNum = { streetNum }\n  }\n\n  fun withStreetNum(streetNum: Yielded\u003cInt\u003e) = apply {\n    this.streetNum = streetNum\n  }\n\n  override fun produce() : Address = Address(\n    city = city,\n    state = state,\n    streetNum = streetNum()\n  )\n}\n```\n\nFrom the above example, we can see that we have two new functions in our `Factory`.\n\nThese functions allow us to override the value generated for `streetNum` to have either\na static value every time we invoke `.produce()`, or a dynamic one. By default, the value\nof it will be a lambda function which delegates to `Random.nextInt()` each time.\n\n### Publishing\n\n* Bump version in `gradle.properties` of `kfactory` module.\n* Execute the following to upload artifact:\n```shell\n$ ./gradlew :kfactory:publish \\\n            --no-daemon --no-parallel \\\n            -Psigning.secretKeyRingFile=\u003ckeyring_file_path\u003e \\\n            -Psigning.password=\u003ckeyring_password\u003e \\\n            -Psigning.keyId=\u003ckeyring_id\u003e \\\n            -PmavenCentralUsername=\u003cnexus_username\u003e \\ \n            -PmavenCentralPassword=\u003cnexus_password\u003e\n```\n\nAfter this operation finishes, you can promote the artifact to be released with:\n```shell\n$ ./gradlew closeAndReleaseRepository \\\n            -PmavenCentralUsername=\u003cnexus_username\u003e \\\n            -PmavenCentralPassword=\u003cnexus_password\u003e\n```\n\n## Maintainers\n\nThe core maintainer of this project, is the Platform Team of [Blueground](https://theblueground.com)!\n\n* [Stratos Pavlakis](https://github.com/th3hunt)\n* [Andreas Gounaris](https://github.com/andrikoz)\n* [Panagiotis Papadopoulos](https://github.com/panagpapad)\n* [Pavlos-Petros Tournaris](https://github.com/pavlospt)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbluegroundltd%2Fkfactory","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbluegroundltd%2Fkfactory","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbluegroundltd%2Fkfactory/lists"}