{"id":31526978,"url":"https://github.com/benwoodworth/parameterize","last_synced_at":"2025-10-03T21:17:15.394Z","repository":{"id":179579882,"uuid":"663262786","full_name":"BenWoodworth/Parameterize","owner":"BenWoodworth","description":"Kotlin syntax for clean parameterized code","archived":false,"fork":false,"pushed_at":"2025-06-29T23:20:05.000Z","size":643,"stargazers_count":41,"open_issues_count":12,"forks_count":3,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-29T23:46:21.103Z","etag":null,"topics":["dsl","kotlin","library","parameterized-tests","testing"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/BenWoodworth.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","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}},"created_at":"2023-07-06T23:29:03.000Z","updated_at":"2025-06-27T02:29:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"9f8f22dd-376b-4572-bc33-bb2e38554edd","html_url":"https://github.com/BenWoodworth/Parameterize","commit_stats":{"total_commits":83,"total_committers":1,"mean_commits":83.0,"dds":0.0,"last_synced_commit":"47a8c4e8f749acd44dc9d44c3ce05c61e87f9771"},"previous_names":["benwoodworth/parameterize"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/BenWoodworth/Parameterize","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenWoodworth%2FParameterize","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenWoodworth%2FParameterize/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenWoodworth%2FParameterize/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenWoodworth%2FParameterize/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/BenWoodworth","download_url":"https://codeload.github.com/BenWoodworth/Parameterize/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/BenWoodworth%2FParameterize/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278228317,"owners_count":25952080,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-03T02:00:06.070Z","response_time":53,"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":["dsl","kotlin","library","parameterized-tests","testing"],"created_at":"2025-10-03T21:17:10.954Z","updated_at":"2025-10-03T21:17:15.387Z","avatar_url":"https://github.com/BenWoodworth.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Parameterize\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.benwoodworth.parameterize/parameterize-core)](https://central.sonatype.com/artifact/com.benwoodworth.parameterize/parameterize-core)\n[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/s/com.benwoodworth.parameterize/parameterize-core?server=https%3A%2F%2Fs01.oss.sonatype.org)](https://s01.oss.sonatype.org/content/repositories/snapshots/com/benwoodworth/parameterize/parameterize-core)\n[![KDoc](https://img.shields.io/badge/api-KDoc-blue)](https://benwoodworth.github.io/Parameterize/parameterize-core/com.benwoodworth.parameterize/parameterize.html)\n[![Kotlin](https://img.shields.io/badge/kotlin-2.2.0-blue.svg?logo=kotlin)](http://kotlinlang.org)\n[![Slack channel](https://img.shields.io/badge/chat-slack-blue.svg?logo=slack)](https://kotlinlang.slack.com/messages/parameterize/)\n\nParameterize is a multiplatform Kotlin library introducing a concise, idiomatic style of parameterizing code. Having\nparameters be declared within the logic, potentially conditionally or with dynamic arguments, it's possible to model\ncomplicated control flow scenarios much more cleanly.\n\n```kotlin\nparameterize {\n    val letter by parameter('a'..'z')\n    val primeUnder20 by parameterOf(2, 3, 5, 7, 11, 13, 17, 19)\n    val computedValue by parameter { slowArgumentsComputation() }\n\n    // ...\n}\n```\n\nWith its default behavior, `parameterize` is strictly an alternative syntax to nested `for` loops, with loop variables\ndefined within the body instead of up front, and without the indentation that's required for additional inner loops.\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003cth\u003eExample \u003ccode\u003eparameterize\u003c/code\u003e loop\u003c/th\u003e\n\u003cth\u003eEquivalent \u003ccode\u003efor\u003c/code\u003e loops\u003c/th\u003e\n\u003c/tr\u003e\n\n\u003ctr\u003e\n\u003ctd\u003e\n\n```kotlin\nval reddishYellows = sequence {\n    parameterize {\n        val red by parameter(128..255)\n        val green by parameter(64..(red - 32))\n        val blue by parameter(0..(green - 64))\n\n        yield(Color(red, green, blue))\n    }\n}\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n```kotlin\nval reddishYellows = sequence {\n    for (red in 128..255) {\n        for (green in 64..(red - 32)) {\n            for (blue in 0..(green - 64)) {\n                yield(Color(red, green, blue))\n            }\n        }\n    }\n}\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\nIn addition to its default behavior, `parameterize` has a configuration with options to decorate its iterations, handle\nand record failures, and summarize the overall loop execution. The flexibility `parameterize` offers makes it suitable\nfor many different specific use cases, including built in ways to access the named parameter arguments when a failure\noccurs, recording failures while continuing to the next iteration, and throwing comprehensive multi-failures that list\nrecorded failures with parameter information.\n\n## Parameterized Testing\n\nWith parameterized testing being the motivating use case for this library, `parameterize` can be used to cleanly and\nmore idiomatically cover edge cases while testing. As an example, here is a test that succinctly covers all the possible\nways a `substring` can be contained within a `string`, running the `parameterize` block once for each case:\n\n```kotlin\nval string = \"prefix-substring-suffix\"  // in the middle\nval string = \"substring-suffix\"         // at the start\nval string = \"prefix-substring\"         // at the end\nval string = \"substring\"                // the entire string\n```\n\n```kotlin\n// See full test suite examples below, with `parameterizeTest {...}` configured for testing\nfun a_string_should_contain_its_own_substring() = parameterizeTest {\n    val substring = \"substring\"\n    val prefix by parameterOf(\"prefix-\", \"\")\n    val suffix by parameterOf(\"-suffix\", \"\")\n\n    val string = \"$prefix$substring$suffix\"\n\n    assertTrue(string.contains(substring), \"\\\"$string\\\".contains(\\\"$substring\\\")\")\n}\n```\n\nIf any of the test cases don't pass, the failures will be wrapped into an `Error` detailing the parameters with their\narguments \u003cins\u003e*and parameter names*\u003c/ins\u003e for each, plus support for JVM tooling with\n[expected/actual value comparison](http://ota4j-team.github.io/opentest4j/docs/current/api/org/opentest4j/AssertionFailedError.html)\nand [multi-failures](http://ota4j-team.github.io/opentest4j/docs/current/api/org/opentest4j/MultipleFailuresError.html):\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003eMulti-failure stack trace\u003c/b\u003e\u003c/summary\u003e\n\n```java\ncom.benwoodworth.parameterize.ParameterizeFailedError: Failed 2/4 cases\n\tAssertionFailedError: \"prefix-substring-suffix\".contains(\"substring\")\n\tAssertionFailedError: \"prefix-substring\".contains(\"substring\")\n\tSuppressed: com.benwoodworth.parameterize.Failure: Failed with arguments:\n\t\tprefix = prefix-\n\t\tsuffix = -suffix\n\tCaused by: org.opentest4j.AssertionFailedError: \"prefix-substring-suffix\".contains(\"substring\")\n\t\tat kotlin.test.AssertionsKt.assertTrue(Assertions.kt:44)\n\t\tat ContainsSpec.a_string_should_contain_its_own_substring(ContainsSpec.kt:18)\n\t\tat org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)\n\tSuppressed: com.benwoodworth.parameterize.Failure: Failed with arguments:\n\t\tprefix = prefix-\n\t\tsuffix = \n\tCaused by: org.opentest4j.AssertionFailedError: \"prefix-substring\".contains(\"substring\")\n\t\tat kotlin.test.AssertionsKt.assertTrue(Assertions.kt:44)\n\t\tat ContainsSpec.a_string_should_contain_its_own_substring(ContainsSpec.kt:18)\n\t\tat org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)\n```\n\n\u003c/details\u003e\n\nThe parameters are designed to be flexible, being able to depend on other parameters, be declared conditionally, or even\nused in a loop to declare multiple parameters from the same property. Features which are especially useful for covering\nedge/corner cases:\n\n```kotlin\n@Test\nfun an_int_should_not_equal_a_different_int() = parameterizeTest {\n    val int by parameterOf(0, 1, -1, Int.MAX_VALUE, Int.MIN_VALUE)\n    val differentInt by parameterOf(int + 1, int - 1)\n\n    assertNotEquals(int, differentInt)\n}\n```\n\n### Test Suite Examples\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e\n    Annotation-based frameworks\n    (\u003ca href=\"https://kotlinlang.org/api/latest/kotlin.test/\"\u003ekotlin.test\u003c/a\u003e,\n    \u003ca href=\"https://junit.org/\"\u003eJUnit\u003c/a\u003e,\n    \u003ca href=\"https://testng.org/\"\u003eTestNG\u003c/a\u003e,\n    ...)\n\u003c/b\u003e\u003c/summary\u003e\n\nUsing the `decorator` configuration option, `parameterize` can be configured to trigger a test framework's before/after\nhooks for each of its iterations. Additionally the `onFailure` handler can be used to record failures and continue to\nthe next iteration, making `parameterize` report a comprehensive multi-failure instead of just throwing. In this\n[kotlin.test](https://kotlinlang.org/api/latest/kotlin.test/) example, `parameterizeTest` wraps a pre-configured\n`parameterize` call to avoid the boilerplate, and have it be easily accessible to any test suite by extending the\n`TestingContext` class:\n\n```kotlin\nabstract class TestingContext {\n    open fun beforeTest() {}\n    open fun afterTest() {}\n\n    // The annotations would be lost when overriding beforeTest/afterTest,\n    // so hook in here instead of relying on the subclasses to apply them.\n    @BeforeTest\n    fun beforeTestHook(): Unit = beforeTest()\n\n    @AfterTest\n    fun afterTestHook(): Unit = afterTest()\n\n\n    protected inline fun parameterizeTest(\n        recordFailures: Long = someDefault, // Example of how `parameterize` could get wrapped,\n        maxFailures: Long = Long.MAX_VALUE, // exposing options according to the testing needs.\n        block: ParameterizeScope.() -\u003e Unit\n    ): Unit = parameterize(\n        // Inserts before \u0026 after calls around each test case,\n        // except where already invoked by the test framework.\n        decorator = { testCase -\u003e\n            if (!isFirstIteration) beforeTest()\n            testCase()\n            if (!isLastIteration) afterTest()\n        },\n\n        onFailure = { failure -\u003e\n            recordFailure = failureCount \u003c= recordFailures\n            breakEarly = failureCount \u003e= maxFailures\n        }\n    ) {\n        block()\n    }\n}\n```\n```kotlin\nclass ContainsSpec : TestingContext() {\n    override fun beforeTest() {\n        // ...\n    }\n\n    override fun afterTest() {\n        // ...\n    }\n    \n    @Test\n    fun a_string_should_contain_its_own_substring() = parameterizeTest {\n        val substring = \"substring\"\n        val prefix by parameterOf(\"prefix-\", \"\")\n        val suffix by parameterOf(\"-suffix\", \"\")\n\n        val string = \"$prefix$substring$suffix\"\n\n        assertTrue(string.contains(substring), \"\\\"$string\\\".contains(\\\"$substring\\\")\")\n    }\n}\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e\n    DSL-based frameworks\n    (\u003ca href=\"https://kotest.io/docs/framework/framework.html\"\u003eKotest\u003c/a\u003e,\n    \u003ca href=\"https://www.spekframework.org/\"\u003eSpek\u003c/a\u003e,\n    \u003ca href=\"https://gitlab.com/opensavvy/prepared\"\u003ePrepared\u003c/a\u003e,\n    ...)\n\u003c/b\u003e\u003c/summary\u003e\n\nWith test frameworks that declare tests dynamically, it's possible to produce a suite of *separate* tests by declaring\na single test within a parameterized group. With Kotest's [Fun Spec](https://kotest.io/docs/framework/testing-styles.html#fun-spec),\nfor example, this code will register four tests that will be reported separately by the test runner when executed, all\ngrouped together under the one test `context`:\n\n```kotlin\n└─ A string should contain its own substring\n   ├─ \"prefix-substring-suffix\".contains(\"substring\")\n   ├─ \"prefix-substring\".contains(\"substring\")\n   ├─ \"substring-suffix\".contains(\"substring\")\n   └─ \"substring\".contains(\"substring\")\n```\n```kotlin\ncontext(\"A string should contain its own substring\") {\n    parameterize {\n        val substring = \"substring\"\n        val prefix by parameterOf(\"prefix-\", \"\")\n        val suffix by parameterOf(\"-suffix\", \"\")\n\n        val string = \"$prefix$substring$suffix\"\n\n        test(\"\\\"$string\\\".contains(\\\"$substring\\\")\") {\n            string.contains(substring) shouldBe true\n        }\n    }\n}\n```\n\nIn the future, it will likely be possible for this to be written more nicely once Kotlin supports decorators, removing\nthe need for an extra level of nesting nesting inside the group of tests.\n([See here](https://youtrack.jetbrains.com/issue/KT-49904/Decorators#focus=Comments-27-8465650.0-0))\n\n\u003c/details\u003e\n\n## Setup\n\n### Modules\n\n| Artifact            | Description                                                                                       |\n|---------------------|---------------------------------------------------------------------------------------------------|\n| `parameterize-api`  | Library primitives, including the `ParameterizeScope` interface and `Parameter` functions.        |\n| `parameterize-core` | The core functionality, with `parameterize {}` as the entry point for running parameterized code. |\n\n\n### Gradle\n\n```kotlin\n// build.gradle.kts\n\nplugins {\n    kotlin(\"jvm\") version \"2.2.0\" // or kotlin(\"multiplatform\"), etc.\n}\n\nrepositories {\n    mavenCentral()\n    //maven(\"https://s01.oss.sonatype.org/content/repositories/snapshots/\")\n}\n\ndependencies {\n    implementation(\"com.benwoodworth.parameterize:parameterize-core:$parameterize_version\") // or testImplementation(...)\n    //api(\"com.benwoodworth.parameterize:parameterize-api:$parameterize_version\") // for libraries that expose parameter DSLs\n}\n```\n\n### A note about stability\n\nWhile Parameterize is in beta, there may be source/binary/behavioral changes in new minor (v0.#.0) releases. Any\nbreaking changes will be documented on release, with\n[automatic replacements](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-deprecated/replace-with.html)\nfor source-breaking changes provided where possible.\n\nThat said, the library is thoroughly tested, and the `parameter` DSL is unlikely to drastically change, with most of the\nlibrary's evolution expected to be isolated to configuration. So in scenarios where binary compatibility isn't a\nconcern, and changes to configuration are acceptable, I consider Parameterize ready to be used in the wild. Of course\nexercise caution with these earlier releases, as they have not yet been battle tested. But as a strong believer of\ndogfooding in software, I am already using it for testing in projects of my own. And in case of any major bugs, I will\nmake sure they are addressed in a timely manner.\n\nI designed the library to address pain points I found in the rigidness of other parameterized/property-based testing\nlibraries, and have been very happy with some new patterns that have emerged from the flexible code that Parameterize\nenables. I'm planning on documenting these at some point, and encourage discussion and code sharing in the Slack channel\nlinked at the top :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenwoodworth%2Fparameterize","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenwoodworth%2Fparameterize","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenwoodworth%2Fparameterize/lists"}