{"id":15383576,"url":"https://github.com/mvysny/dynatest","last_synced_at":"2025-06-21T09:38:57.918Z","repository":{"id":57721686,"uuid":"120896466","full_name":"mvysny/dynatest","owner":"mvysny","description":"Simplest \u0026 Powerful Testing Framework For Kotlin. No annotations!","archived":false,"fork":false,"pushed_at":"2024-08-26T09:57:28.000Z","size":651,"stargazers_count":21,"open_issues_count":2,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-16T21:03:29.764Z","etag":null,"topics":["junit5","kotlin","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/mvysny.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","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}},"created_at":"2018-02-09T11:14:48.000Z","updated_at":"2025-02-26T20:35:37.000Z","dependencies_parsed_at":"2023-09-22T22:07:55.132Z","dependency_job_id":"8080fc08-7943-44fe-983b-08155f585a52","html_url":"https://github.com/mvysny/dynatest","commit_stats":{"total_commits":378,"total_committers":4,"mean_commits":94.5,"dds":"0.43386243386243384","last_synced_commit":"0c8bb33bbcab9707161307f4e8e7ec35fcd11213"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/mvysny/dynatest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvysny%2Fdynatest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvysny%2Fdynatest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvysny%2Fdynatest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvysny%2Fdynatest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mvysny","download_url":"https://codeload.github.com/mvysny/dynatest/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mvysny%2Fdynatest/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261101617,"owners_count":23109860,"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","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":["junit5","kotlin","testing"],"created_at":"2024-10-01T14:38:49.666Z","updated_at":"2025-06-21T09:38:52.905Z","avatar_url":"https://github.com/mvysny.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DynaTest Dynamic Testing\n\n[![GitHub tag](https://img.shields.io/github/tag/mvysny/dynatest.svg)](https://github.com/mvysny/dynatest/tags)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.mvysny.dynatest/dynatest-engine/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.mvysny.dynatest/dynatest-engine)\n[![Build Status](https://github.com/mvysny/dynatest/actions/workflows/gradle.yml/badge.svg)](https://github.com/mvysny/dynatest/actions/workflows/gradle.yml)\n\n## DEPRECATED\n\nThis framework is DEPRECATED, will no longer be maintained. Leaving the source code on GitHub as a reference.\nSee at the bottom of this file for reasons to deprecate, and for migration tips.\n\nThe original README follows.\n\n## DynaTest\n\nThe simplest and most powerful testing framework for Kotlin.\n\nWe promote builders over annotations. Instead of having annotations forming an unfamiliar\n[embedded mini-language, interpreted by magic at runtime](https://blog.softwaremill.com/the-case-against-annotations-4b2fb170ed67),\nwe let you create your test methods using Kotlin - an actual programming language familiar to you.\n\nWe don't program in annotations, after all. We program in Kotlin.\n\nJava compatibility matrix:\n* DynaTest 0.19 and lower require Java 7+\n* DynaTest 0.20+ require Java 8+\n\n## Example Code\n\nCode example of the [CalculatorTest.kt](dynatest-engine/src/test/kotlin/com/github/mvysny/dynatest/CalculatorTest.kt):\n\n```kotlin\nclass Calculator {\n    fun plusOne(i: Int) = i + 1\n    fun close() {}\n}\n\n/**\n * A test case.\n */\nclass CalculatorTest : DynaTest({\n\n    /**\n     * Top-level test.\n     */\n    test(\"calculator instantiation test\") {\n        Calculator()\n    }\n\n    // you can have as many groups as you like, and you can nest them\n    // 'group' has no semantic definition, you are free to assign one as you need\n    group(\"tests the plusOne() function\") {\n\n        // demo of the very simple test\n        test(\"one plusOne\") {\n            expect(2) { Calculator().plusOne(1) }\n        }\n\n        // nested group\n        group(\"positive numbers\") {\n            // you can even create a reusable test battery, call it from anywhere and use any parameters you like.\n            calculatorBattery(0..10)\n            calculatorBattery(100..110)\n        }\n\n        group(\"negative numbers\") {\n            calculatorBattery(-50..-40)\n        }\n    }\n})\n\n/**\n * Demonstrates a reusable test battery which can be called repeatedly and parametrized.\n * @receiver all tests+groups do not run immediately, but instead they register themselves to this group; they are run later on\n * when launched by JUnit5\n * @param range parametrized battery demo\n */\n@DynaTestDsl\nfun DynaNodeGroup.calculatorBattery(range: IntRange) {\n    require(!range.isEmpty())\n\n    group(\"plusOne on $range\") {\n        lateinit var c: Calculator\n\n        // analogous to @Before in JUnit4, or @BeforeEach in JUnit5\n        beforeEach { c = Calculator() }\n        // analogous to @After in JUnit4, or @AfterEach in JUnit5\n        afterEach { c.close() }\n\n        // we can even generate test cases in a loop\n        for (i in range) {\n            test(\"plusOne($i) == ${i + 1}\") {\n                expect(i + 1) { c.plusOne(i) }\n            }\n        }\n    }\n}\n```\n\nRunning this in your IDE will produce:\n\n![DynaTest CalculatorTest screenshot](images/dynatest.png)\n\n## Tips \u0026 Tricks\n\nIntellij by default runs tests using Gradle. Dynatest contains workaround around\nGradle and Intellij bugs, disabling navigation to the test source code from the\n*Run Tests* Intellij window. To fix this issue, head to *File* / *Settings* /\n*Build, Execution, Deployment* / *Build Tools* / *Gradle* and make sure that\nthe *Run tests using* option is set to Intellij.\n\n## Using DynaTest In Your Projects\n\n### Example Project\n\nPlease see [karibu-helloworld-application](https://github.com/mvysny/karibu-helloworld-application)\nfor a very simple Gradle-based project employing DynaTest, to see how to integrate DynaTest\nwith your app.\n\n### Gradle+DynaTest Integration Guide\n\nDynaTest runs on top of JUnit5 engine, but it ignores any JUnit5 tests and only runs `DynaTest` tests. As a first step,\nadd the test dependency on this library to your `build.gradle` file:\n\n```groovy\nrepositories {\n    mavenCentral()\n}\ndependencies {\n    testCompile(\"com.github.mvysny.dynatest:dynatest:x.y\")\n}\n```\n\n\u003e Note: check the tag number on the top for the newest version\n\nDynaTest will transitively include JUnit5's core.\n\nIf you are using older Gradle 4.5.x or earlier which does not have native support for JUnit5, to actually\nrun the tests you will need to add the [junit5-gradle-consumer](https://github.com/junit-team/junit5-samples/tree/r5.0.3/junit5-gradle-consumer)\nplugin to your build script. Please see the plugin's documentation for details.\n\nIf you are using Gradle 4.6 or later, JUnit5 support is built-in; all you need to enable it is to insert this into your `build.gradle` file:\n\n```groovy\ntest {\n    useJUnitPlatform()\n}\n```\nor `build.gradle.kts`:\n```kotlin\ntasks.withType\u003cTest\u003e {\n    useJUnitPlatform()\n}\n```\n\n(more on Gradle's JUnit5 support here: [Gradle JUnit5 support](https://docs.gradle.org/4.6-rc-1/release-notes.html#junit-5-support))\n\nIf you want to run JUnit5 tests along the DynaTest tests as well, you can run both DynaTest test engine along with JUnit5 Jupiter engine\n(which will only run JUnit5 tests and will ignore DynaTest tests):\n\n```groovy\ndependencies {\n    testRuntime(\"org.junit.jupiter:junit-jupiter-engine:5.8.2\")\n}\n```\n\n## DynaTest Guide\n\nDynaTest is composed of just 6 methods (and 0 annotations).\n\nCalling the `test(\"name\") { test body }` function creates a new test and schedules it to be run by JUnit5 core. Example:\n```kotlin\nclass MyTest : DynaTest({\n  test(\"'save' button saves data\") {\n    button.click()\n    expect(1) { Person.findAll().size }\n  }\n})\n```\n\nCalling the `group(\"name\") { register more groups and tests }` function creates a test group and allows you to define tests (or\nmore groups) inside of it. By itself the group does nothing more than nesting tests in your IDE output when you run\ntests; however it becomes very powerful with the lifecycle methods `beforeGroup`/`afterGroup`. Example:\n```kotlin\nclass MyTest : DynaTest({\n  group(\"String.length tests\") {\n    test(\"Empty string has zero length\") { \n      expect(0) { \"\".length }\n    }\n  }\n})\n```\n\n\u003e Technical detail: You write your test suite by extending the `DynaTest` class. The DynaTest constructor runs a block\nwhich allows you to register tests and groups.\nThe block is pure Kotlin so you can use for loops, reusable functions and all features of the Kotlin programming language.\n\n### Test Lifecycle Listeners\n\nOften you need to prepare some kind of environment before a test, and tear it down afterwards. For that simply call\nthe following functions:\n\n`beforeEach { body }` schedules given block to run before every individual test, both in this group and in all subgroups. If the body fails,\nthe test is not run and is marked failed. Example:\n```kotlin\nclass CalculatorTest : DynaTest({\n    lateinit var calculator: Calculator\n    beforeEach { calculator = Calculator() }\n    afterEach { calculator.close() }\n    \n    test(\"0+1=1\") {\n      expect(1) { calculator.plusOne(0) }\n    }\n})\n```\n\n`afterEach { body }` schedules given block to run after every individual test, both in this group and in all subgroups.\nThe body is ran even if the test itself or the `beforeEach` block failed. See `beforeEach` for an example.\n\n`beforeGroup { body }` schedules given block to run once before every test in this group. The block won't run again for subgroups.\nIf the block fails, no tests/beforeEach/afterEach from this group and its subgroups are executed and they will be all marked as failed. This is a\ndirect replacement for `@BeforeClass` in JUnit, but it is a lot more powerful since you can use it on subgroups as well. You\ntypically use `beforeGroup` to start something that is expensive to construct/start, e.g. a Jetty server:\n```kotlin\nclass ServerTest : DynaTest({\n    lateinit var server: Server\n    beforeGroup { server = Server(8080); server.start() }\n    afterGroup { server.stop() }\n    \n    test(\"ping\") {\n      expect(\"OK\") { URL(\"http://localhost:8080/status\").readText() }\n    }\n})\n```\n\n`afterGroup { body }` schedules given block to run after the group concluded running its tests, both in this group and in all subgroups.\nThe body is ran even if the test itself, or any `beforeEach`/`afterEach` or even `beforeGroup` blocks failed.\nSee `beforeGroup` for an example.\n\n### Conclusion\n\nNow you have a good understanding of all the machinery DynaTest has to offer. This is completely enough for simple tests.\nNow we move to advanced topics on how to put this machinery to good use for more advanced scenarios.\n\n## File/Directory Utilities\n\ndynatest 0.18 and later contains support for filesystem-related assertions and ops:\n\n* `File.expectExists()`\n* `File.expectNotExists()`\n* `File.expectFile()`\n* `File.expectDirectory()`\n* `File.expectReadableFile()`\n* `File.expectWritableFile()`\n\nYou can use Kotlin built-in `createTempDir()` and `createTempFile()` global functions to create temporary\nfolders and files; use Kotlin built-in `copyRecursively()` to copy entire folders.\n\nIf you need to assert that given folder contains certain amount of files, use the\n`File.expectFiles()` function as follows:\n\n* `File(\"build\").expectFiles(\"generated/**/*.java\", 40..50)`\n\n### Temporary Directories \n\nYou can use the `withTempDir()` helper function to create a test folder before every test,\nthen tear it down afterwards:\n\n```kotlin\ngroup(\"source generator tests\") {\n  val sources: File by withTempDir(\"sources\")\n  test(\"simple\") {\n    generateSourcesTo(sources)\n    val generatedFiles: List\u003cFile\u003e = sources.expectFiles(\"*.java\", 10..10)\n    // ...\n  }\n  test(\"more complex test\") {\n    // 'sources' will point to a new temporary directory now.\n    generateSourcesTo(sources)\n    // ...\n  }\n}\n```\n\nTo create a reusable utility function which e.g. pre-populates the directory, you have\nto use a different syntax:\n\n```kotlin\n@DynaTestDsl\nfun DynaNodeGroup.withSources(): ReadWriteProperty\u003cAny?, File\u003e {\n  val sourcesProperty: ReadWriteProperty\u003cAny?, File\u003e = withTempDir(\"sources\")\n  val sources by sourcesProperty\n  beforeEach {\n    generateSourcesTo(sources)\n  }\n  return sourcesProperty\n}\n\ngroup(\"source generator tests\") {\n  val sources: File by withSources()\n  test(\"simple\") {\n    sources.expectFiles(\"*.java\", 10..10)\n  }\n}\n```\n\nMake sure to never return `sources` since that would query the value of the `sourcesProperty`\nright away, failing with `unitialized` `RuntimeException`.\n\nAlternatively, since DynaTest 0.22 you can take advantage of `withTempDir()`'s init block:\n\n```kotlin\n@DynaTestDsl\nfun DynaNodeGroup.withSources(): ReadWriteProperty\u003cAny?, File\u003e =\n  withTempDir(\"sources\") { dir -\u003e generateSourcesTo(dir) }\n\ngroup(\"source generator tests\") {\n  val sources: File by withSources()\n  test(\"simple\") {\n    sources.expectFiles(\"*.java\", 10..10)\n  }\n}\n```\n\n### Lazy-init variables in general\n\nThe above examples served only for a specific case of having a temporary dir accessible\nto a bunch of tests. However, you can take advantage of the `LateinitProperty` class\nto create any kind of variable. For example, say that we have a `TestProject`\nclass which internally creates a temp folder and sets up some kind of a test project, then deletes it afterwards:\n\n```kotlin\n@DynaTestDsl\nfun DynaNodeGroup.withTestProject(): ReadWriteProperty\u003cAny?, TestProject\u003e {\n    val testProjectProperty = LateinitProperty\u003cTestProject\u003e(\"testproject\")\n    var testProject: TestProject by testProjectProperty\n    beforeEach {\n        testProject = TestProject()\n        println(\"Test project directory: ${testProject.dir}\")\n    }\n    afterEach {\n        // comment out if a test is failing and you need to investigate the project files.\n        testProject.delete()\n    }\n    return testProjectProperty\n}\n\nclass MiscTest : DynaTest({\n  val testProject: TestProject by withTestProject()\n\n  test(\"something\") {\n    testProject.buildFile.writeText(\"something\")\n  }\n})\n```\n\nTo build upon such lazy-init variable (for example to create a test project which comes\npre-populated with some testing files), you have to use the following construct:\n\n```kotlin\n@DynaTestDsl\nfun DynaNodeGroup.withHelloWorldJavaTestProject(): ReadWriteProperty\u003cAny?, File\u003e {\n  val testProjectProperty = withTestProject()\n  val testProject: TestProject by testProjectProperty\n  beforeEach {\n    testProject.buildFile.writeText(\"\"\"plugins { id 'java' }\"\"\")\n  }\n  return testProject\n}\n\ngroup(\"hello world java examples\") {\n  val project: TestProject by withHelloWorldJavaTestProject()\n  test(\"simple\") {\n    project.build(\"jar\")\n  }\n}\n```\n\n## Other utility functions\n\n* `jvmVersion` variable will return the major JVM version of the current JRE, e.g. 6 for Java 1.6, 8 for Java 8, 11 for Java 11 etc.\n\n## Advanced Topics\n\n### Conditional tests\n\nRemember, the block is pure Kotlin code. In fact it is a mini-DSL language, creating tests and groups.\nYou call the `test()` function to create/register a test; if you don't call the function the test is simply\nnot created. For example:\n\n```kotlin\nclass NativesTest : DynaTest({\n  if (OS.isLinux()) {\n    test(\"linux-based test\") {\n      // run tests only on Linux.\n    }\n  }\n})\n```\n\nThe `if (OS.isLinux())` is just a simple Kotlin `if()` followed by a call to the `isLinux()` function.\n\n### Disabling tests temporarily\n\nUse `xtest{}` or `xgroup{}` to temporarily disable a test (since dynatest 0.20):\n\n```kotlin\nclass DisabledTest : DynaTest({\n  xtest(\"not run\") {}\n  xgroup(\"no child tests are run\") {\n    xtest(\"not run\") {}\n    test(\"also not run\") {}\n  }\n})\n```\n\n### Reusable test battery\n\nRemember that the `test()`/`group()` are just plain Kotlin functions, which register a test or a group.\nTypically you call those functions from the block passed into the\n`DynaTest` constructor, but you can call them from anywhere - you can extract a reusable function that registers some kind\nof reusable battery of tests. The only thing that's needed is the `DynaNodeGroup` receiver (to have a context from\nwhich you can call the `test()`/`group()`/other functions).\n\nTherefore, in order to create a reusable test battery, you can simply create a (possibly parametrized) function which\nruns in the context of the\n`DynaNodeGroup`. That allows the function to create test groups and tests as necessary:\n\n```kotlin\n@DynaTestDsl\nfun DynaNodeGroup.layoutTestBattery(clazz: Class\u003cout ComponentContainer\u003e) {\n  group(\"tests for ${clazz.simpleName}\") {\n    lateinit var layout: ComponentContainer\n    beforeEach { layout = clazz.newInstance() }\n    \n    test(\"Adding a component will make the count go to 1\") {\n      layout.addComponent(Label(\"Hello World\"))\n      expect(1) { layout.getComponentCount() }\n    }\n  }\n}\n\nclass LayoutTest : DynaTest({\n  layoutTestBattery(VerticalLayout::class.java)\n  layoutTestBattery(HorizontalLayout::class.java)\n  layoutTestBattery(CssLayout::class.java)\n  layoutTestBattery(FlexLayout::class.java)\n})\n```\n\n### Plugging in into the test life-cycle\n\nSay that you want to mock the database and clean it before and after every test. Very easy: just extract the\nlifecycle-controlling functions into a separate function:\n\n```kotlin\n@DynaTestDsl\nfun DynaNodeGroup.usingDatabase() {\n\n    beforeGroup {\n        VaadinOnKotlin.dataSourceConfig.apply {\n            minimumIdle = 0\n            maximumPoolSize = 30\n            this.jdbcUrl = \"jdbc:h2:mem:test;DB_CLOSE_DELAY=-1\"\n            this.username = \"sa\"\n            this.password = \"\"\n        }\n        Sql2oVOKPlugin().init()\n        db {\n            con.createQuery(\"\"\"create table if not exists Test (\n                id bigint primary key auto_increment,\n                name varchar not null,\n                age integer not null,\n                dateOfBirth date,\n                created timestamp,\n                alive boolean,\n                maritalStatus varchar\n                 )\"\"\").executeUpdate()\n        }\n    }\n\n    afterGroup {\n        Sql2oVOKPlugin().destroy()\n    }\n\n    fun clearDatabase() { Person.deleteAll() }\n    beforeEach { clearDatabase() }\n    afterEach { clearDatabase() }\n}\n```\n\nThis sample is taken from Vaadin-on-Kotlin [EntityDataProviderTest.kt](https://github.com/mvysny/vaadin-on-kotlin/blob/master/vok-framework-sql2o/src/test/kotlin/com/github/vok/framework/sql2o/vaadin/EntityDataProviderTest.kt) file,\nwhich is somewhat complex.\n\nYour typical test will look like this:\n\n```kotlin\nclass SomeOtherEntityTest : DynaTest({\n\n    usingDatabase()\n    \n    test(\"calculating average age\") {\n        db { \n            for (i in 0..10) { Person(age = i).save() }\n        }\n        expect(5) { Person.averageAge() }\n    }\n})\n```\n\nHead to [vok-orm](https://github.com/mvysny/vok-orm) on how to use the database in this fashion from your app.\n\n### Real-world Web App Example\n\nA testing bootstrap in your application will be a lot simpler. See the following example taken from\nthe [Vaadin Kotlin PWA Demo](https://github.com/mvysny/vaadin-kotlin-pwa):\n\n```kotlin\nclass MainViewTest: DynaTest({\n    beforeGroup { Bootstrap().contextInitialized(null) }\n    afterGroup { Bootstrap().contextDestroyed(null) }\n    beforeEach { MockVaadin.setup(autoDiscoverViews(\"com.vaadin.pwademo\")) }\n    beforeEach { Task.deleteAll() }\n    afterEach { Task.deleteAll() }\n\n    test(\"add a task\") {\n        UI.getCurrent().navigateTo(\"\")\n        _get\u003cTextField\u003e { caption = \"Title:\" } .value = \"New Task\"\n        _get\u003cButton\u003e { caption = \"Add\" } ._click()\n        expectList(\"New Task\") { Task.findAll().map { it.title } }\n        expect(1) { _get\u003cGrid\u003c*\u003e\u003e().dataProvider._size() }\n    }\n})\n```\n\n# DynaTest Design principles\n\nA.K.A The Boring Stuff. We:\n\n* Promote component-oriented programming. You should be able to create a test suite as a component,\n  and simply include that test suite anywhere you see fit.\n* Let programmer use a familiar general-purpose language (Kotlin) to define tests.\n  Put the programmer in charge and allow him to use the well-known language tools and software practices\n  he's already using, in order to create well-maintainable test code.\n* Dissuade from abominable programming techniques like inheritance and annotatiomania.\n  Don't force the developer to use a mini-language based on annotations, for which there\n  are no tools.\n* With great power comes great responsibility. Don't make the test structure generation code more complex\n  than anything else in your project. Keep it simple.\n* No outside lifecycle listeners. Init belongs close to the test. Instead of external listener interfaces we\n  promote `beforeEach`/`afterEach` and `beforeGroup`/`afterGroup`\n\nWhat this framework is not:\n\n* BDD. What BDD strives for is to describe a behavior of an app using the English language.\n  What it really does is that it provides\n  a really lousy, confusing and computationally weak way of writing test programs.\n  There are no good examples to write BDD - all BDD test suites will degrade into\n  an unmaintainable mess. For bad examples, head to\n  [JBehave](http://jbehave.org/). To experience a real horror, head to [Robot Framework](http://robotframework.org/).\n* Spec. What spec strives for is to describe a specification of an app. What it really does is that it provides\n  a lousy way of writing test programs. If you want spec, use [Spek](http://spekframework.org/).\n\nWith DynaTest, you give any meaning you need to groups and tests you need.\n\n## Project Structure\n\nThe project consists of three modules.  First two modules are as thin as possible,\nonly focus on getting the most basic test DSL machinery in place, and makes sure\nthe basics run solidly with Gradle and the JUnit plugin:\n\n* [dynatest-api](dynatest-api) defines the basic test DSL functions like `test{}` and `group{}`.\n* [dynatest-engine](dynatest-engine) focuses on getting the tests created by the DSL to run on top of JUnit.\n\nThe third and final module, [dynatest](dynatest), provides all of the utility functions and helpers.\nSince we can now rely on the fact that the dynatest-engine works properly, this module tests itself using the dynatest-engine.\n\n# Oh God Not Yet Another Testing Framework\n\nI feel you. I hate creating frameworks. It takes a lot of time and\nenergy to create DynaTest, maintain it, test it, document it, find workarounds\nfor Intellij bugs. I'd much rather spend that energy elsewhere.\n\nUnfortunately, all other functional-style testing frameworks suck.\n\n## Comparison With JUnit\n\nTraditional JUnit/TestNG approach is to have a bunch of test classes with `@Test`-annotated methods. That's not bad per se,\nbut it would seem as if the ultimate JUnit's goal was that the test collection must be *pre-known* -\ncomputable by static-analyzing class files alone,\nwithout running any test generator code whatsoever. This is great for IDEs: IDE can immediately\nfigure out all the test methods and will allow you an easy way to run individual tests.\nOn the other side, with this approach,\nthe possibilities to create tests dynamically (e.g. creating a reusable test suite) are severely limited.\nI believe this requirement *reduces the possibilities* of how to structure test code and *promotes bad practices*:\n\nWith JUnit:\n* You simply can't create a parametrized test suite class as a component, instantiating it with various parameters and running it as needed,\n  with parameters supplied in the call site.\n* Annotations are weak - they don't have the full computative power of a proper imperative programming language; attempts to use annotations to\n  express any non-trivial logic leads to annotation overuse and that leads to horrible constructs and [annotatiomania](http://annotatiomania.com/).\n* Reuse of test suites is only possible by the means of inheritance (having a base class with tests, and a bunch of classes\n  extending that base class, parametrizing it with constructors). That leads to deep inheritance hierarchies, which typically lead to spaghetti code.\n  Reusing code as components typically leads to much better separated code with clear boundaries.\n* Even worse than inheritance, it is possible to \"reuse\" test suites by the means of interface mixins. That's a whole new level\n  of inheritance hell.\n\nIt's not just unicorns - DynaTest has disadvantages when compared to JUnit:\n\n* There is no clear distinction between the code that *creates* tests (calls the `test()` method to create a test), and\n  the *testing* code itself (blocks inside of `test()` method). However, there is a ton of difference:\n  those two code bases run at completely different time. Furthermore Kotlin allows to share variables\n  freely between those two code bases, which may create a really dangerous code which fails in mysterious ways.\n  That's magic which must be removed. See [Issue #1](https://github.com/mvysny/dynatest/issues/1) for more details.\n* Weak IDE (Intellij) integration:\n  * \"Rerun failed tests\" always runs all tests\n  * You can't run just single test: in the test source file there is no \"gutter green arrow\" to run the test; also right-clicking the `test()` function\n    in your test class does nothing. You can only run the whole suite :(\n  * It's impossible to re-run single test only from Intellij's Test Run window -\n    right-clicking on the test name either doesn't offer such option, or you'll experience weird behavior like\n    no tests being run at all, or all tests from test class will run etc.\n\nThere's a [IDEA-169198](https://youtrack.jetbrains.com/issue/IDEA-169198) bug report for Intellij, please go there and vote\nto get it resolved.\n\n## Comparison With KoTest\n\nCompared to [KoTest](https://github.com/kotest/kotest), DynaTest\nonly pushes for the `FunSpec` testing style. This may not necessary be bad: DynaTest's\ncodebase is way simpler and it limits the variety of testing styles you can encounter in projects.\n\nDynaTest-KoTest similarities:\n* KoTest offers similar testing style named `FunSpec`; to create a test simply call\n  the `test{}` function; to group tests simply call the `context{}` function in KoTest\n  or `group{}` function in DynaTest.\n\nDynaTest advantages:\n* KoTest doesn't support `beforeGroup{}`/`afterGroup{}`. Yes, there is `beforeContainer{}` and\n  `afterContainer{}` but it works much differently: DynaTest's `beforeGroup{}`/`afterGroup{}`\n  only triggers for the parent group (and not for subgroups), allowing the group itself\n  to control its environment (e.g. start a server before all tests, then tear it down).\n  KoTest's `before/afterContainer{}` is run for every\n  child+grandchild context and it's unclear what its purpose is. There's `beforeSpec{}`/`afterSpec{}`\n  but it always works in the scope of the entire test class; moreover `beforeSpec{}`\n  never seems to be called (a bug?). DynaTest replaces all of that with a simple `beforeGroup{}`/`afterGroup{}`.\n  In short, DynaTest is simpler, much easier to understand\n  and the granularity of control is much better.\n* KoTest `tempfile()`/`tempdir()` creates one temp file/dir for all tests within\n  a test class. If one test fails, you can't inspect the folder contents since the follow-up\n  tests have overwritten the folder contents. DynaTest's `withTempDir()` creates\n  a fresh test folder for every test, and prints the full path to the test folder in case\n  of a test failure - much more pleasant.\n\nDynaTest disadvantages:\n* Navigation to sources sometimes doesn't work, because of [IDEA-169198](https://youtrack.jetbrains.com/issue/IDEA-169198).\n  KoTest is also affected but it offers a native plugin for Intellij, working around the bug.\n* KoTest is official and maintained (probably) by the Kotlin folk, while\n  DynaTest is maintained by \"A Random Internet Dude\".\n\n# License\n\nLicensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html)\n\nCopyright 2017-2018 Martin Vysny\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this software except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n# Contributing / Developing\n\nSee [Contributing](CONTRIBUTING.md).\n\n# Deprecated\n\nThis library is now deprecated. The integration with Intellij IDEA was always quite weak:\n\n1. I have no time to develop a plugin for IDEA\n2. IDEA always contained a couple of bugs that prevented test filtering, or running individual tests;\n   sometimes navigation to test source wouldn't work.\n\nMoreover, the `@Nested` JUnit5 feature fully replaces DynaTest groups, which means that\nDynaTest doesn't bring huge advantages to the table anymore.\n\nTo migrate your project:\n\n1. Change `DynaTest` classes to regular Java classes\n2. Change `test{}` to `@Test`-annotated functions\n3. Change `group{}` to a `@Nested inner class FooTests {}`\n4. Change `DynaNodeGroup.reusableTests()` to `abstract class AbstractReusableTests{}`,\n   then use it in other test classes as `@Nested inner class ReusableTests : AbstractReusableTests()`.\n   Parameters passed to `reusableTests()` can be converted to class properties.\n5. Change `beforeGroup{}` to `@BeforeAll`-annotated function within respective `@Nested` class. The function needs to be static which\n   would require you to use cumbersome `companion object{}`; as a workaround you can\n   use `@TestInstance(TestInstance.Lifecycle.PER_CLASS)` which modifies the test behavior a bit, but\n   the `@BeforeAll` functions no longer need to be static.\n6. Replace `if() { test(\"foo\"){} }` conditional tests with `Assumptions`.\n7. Replace `if() { group(\"foo\"){} }` conditional groups with `Assumptions` called from `@Nested` class constructor.\n   If the assumption fails, the entire class is skipped.\n\nPlease experiment with the `@Nested` class concept - it is very powerful and strongly supported by\nIntellij IDEA. Please consult JUnit 5 documentation for more details.\n\nNow, why am I endorsing an annotation-based framework when I [hate annotations](https://mvysny.github.io/post-annotation-programming/)?\nExcellent question. True, runtime annotations are evil since the annotation processor is detached from the definition of annotations.\nBut, similarly the test runtime is detached from the place where tests are defined, both with annotations\nand with the `test{}` function. Also, JUnit is a well-tested industry standard with excellent IDEA plugin,\nso there's (hopefully) little room for error on programmer side. Also - and this is crucial - JUnit the framework,\nthe documentation and the community is someone else's responsibility. I still think that\nDynatest's functional approach is much more ellegant than thousands of JUnit annotations (especially the parametrized tests),\nbut the IDE support is just as important and is much better at JUnit's side.\n\nThere's one thing that JUnit got better - see the JUnit comparison, the 'disadvantages' section for more details.\n\nTherefore, with heavy heart, I'm deprecating Dynatest.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvysny%2Fdynatest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmvysny%2Fdynatest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmvysny%2Fdynatest/lists"}