{"id":13804173,"url":"https://github.com/atilaneves/unit-threaded","last_synced_at":"2026-02-17T22:32:10.957Z","repository":{"id":9370063,"uuid":"11226361","full_name":"atilaneves/unit-threaded","owner":"atilaneves","description":"Advanced unit test framework for D","archived":false,"fork":false,"pushed_at":"2025-11-13T19:53:26.000Z","size":1565,"stargazers_count":121,"open_issues_count":3,"forks_count":38,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-11-13T21:21:26.669Z","etag":null,"topics":["d","dlang","dlanguage","integration-testing","mock","mocking","property-based-testing","property-testing","testing","unit-testing","unittest"],"latest_commit_sha":null,"homepage":"","language":"D","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/atilaneves.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,"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}},"created_at":"2013-07-07T00:30:11.000Z","updated_at":"2025-08-20T15:57:49.000Z","dependencies_parsed_at":"2024-05-01T21:38:19.004Z","dependency_job_id":"f0ed3ae5-c3e4-41b9-8913-6ab721ab5e26","html_url":"https://github.com/atilaneves/unit-threaded","commit_stats":null,"previous_names":[],"tags_count":178,"template":false,"template_full_name":null,"purl":"pkg:github/atilaneves/unit-threaded","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atilaneves%2Funit-threaded","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atilaneves%2Funit-threaded/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atilaneves%2Funit-threaded/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atilaneves%2Funit-threaded/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atilaneves","download_url":"https://codeload.github.com/atilaneves/unit-threaded/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atilaneves%2Funit-threaded/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29560793,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-17T21:50:49.831Z","status":"ssl_error","status_checked_at":"2026-02-17T21:46:15.313Z","response_time":100,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["d","dlang","dlanguage","integration-testing","mock","mocking","property-based-testing","property-testing","testing","unit-testing","unittest"],"created_at":"2024-08-04T01:00:43.198Z","updated_at":"2026-02-17T22:32:10.931Z","avatar_url":"https://github.com/atilaneves.png","language":"D","readme":"unit-threaded\n=============\n\n[![Build Status](https://github.com/atilaneves/unit-threaded/workflows/CI/badge.svg)](https://github.com/atilaneves/unit-threaded/actions)\n[![Coverage](https://codecov.io/gh/atilaneves/unit-threaded/branch/master/graph/badge.svg)](https://codecov.io/gh/atilaneves/unit-threaded)\n\n[My DConf2016 Lightning talk demonstrating unit-threaded](https://www.youtube.com/watch?v=vNPb4Mg6F6Y#t=6m50s).\n\nMulti-threaded advanced unit test framework for [the D programming language](https://dlang.org/).\n\nAugments D's `unittest` blocks with:\n\n* Tests can be named and individually run\n* Custom assertions for better error reporting (e.g. `1.should == 2`)\n* Runs in threads by default\n* UDAs for customisation of tests\n* Property based testing\n* Mocking\n\n\n## Quick start with dub\n\nNote: while getting started this way is easy, it also increases build\ntimes and may run into edge cases. See below for how to do it manually.\n\ndub runs tests with `dub test`. Unfortunately, due to the nature of\nD's compile-time reflection, to use this library a test runner file\nlisting all modules to reflect on must exist. Since this is a tedious\ntask and easily automated, unit-threaded has a dub configuration\ncalled `gen_ut_main` to do just that.  To use unit-threaded with a dub\nproject, you can use a `unittest` configuration as exemplified in this\n`dub.json`:\n\n```json\n{\n    \"name\": \"myproject\",\n    \"targetType\": \"executable\",\n    \"targetPath\": \"bin\",\n    \"configurations\": [\n        { \"name\": \"executable\" },\n        {\n            \"name\": \"unittest\",\n            \"targetType\": \"executable\",\n            \"preBuildCommands\": [\"$DUB run --compiler=$$DC unit-threaded -c gen_ut_main -- -f bin/ut.d -d $DUB\"],\n            \"mainSourceFile\": \"bin/ut.d\",\n            \"excludedSourceFiles\": [\"src/main.d\"],\n            \"dependencies\": {\n                \"unit-threaded\": \"*\"\n            }\n        }\n    ]\n}\n```\n\nWith `dub.sdl`:\n\n```\nconfiguration \"executable\" {\n}\n\nconfiguration \"unittest\" {\n    dependency \"unit-threaded\" version=\"*\"\n    mainSourceFile \"bin/ut.d\"\n    excludedSourceFiles \"src/main.d\"\n    targetType \"executable\"\n    preBuildCommands \"$DUB run --compiler=$$DC unit-threaded -c gen_ut_main -- -f bin/ut.d -d $DUB\"\n}\n\n```\n\n`excludedSourceFiles` is there to not compile the file containing the\n`main` function to avoid linker errors. As an alternative to using\n`excludedSourceFiles`, the \"real\" `main` can be versioned out:\n\n```d\nversion(unittest) {\n    import unit_threaded;\n    mixin runTestsMain!(\n        \"module1\",\n        \"module2\",\n        // ...\n    );\n} else {\n    void main() {\n        //...\n    }\n}\n```\n\n### Manually listing the D modules with tests\n\nAlternatively to the above and the recommended way is to manually (unfortunately)\nlist all the modules with tests in the unittest main function. There's a mixin\nfor that:\n\n```d\nimport unit_threaded;\nmixin runTestsMain!(\n    \"mypkg.mymod0\",\n    \"mypkg.mymod1\",\n    // ...\n);\n```\n\nYour unittest blocks will now be run in threads and can be run individually.\nTo name each unittest, simply attach a string UDA to it:\n\n```d\n@(\"Test that 2 + 3 is 5\")\nunittest {\n    assert(2 + 3 == 5);\n}\n```\n\nYou can also have multiple configurations for running unit tests, e.g. one that uses\nthe standard D runtime unittest runner and one that uses unit-threaded:\n\n\n    \"configurations\": [\n        {\"name\": \"ut_default\"},\n        {\n          \"name\": \"unittest\",\n          \"preBuildCommands: [\"$DUB run --compiler=$$DC unit-threaded -c gen_ut_main -- -f bin/ut.d -d $DUB\"],\n          \"mainSourceFile\": \"bin/ut.d\",\n          ...\n        }\n    ]\n\n\nIn this example, `dub test -c ut_default` runs as usual if you don't use this\nlibrary, and `dub test` runs with the unit-threaded test runner.\n\nTo use unit-threaded's assertions or UDA-based features, you must import the library:\n\n```d\n// Don't use `version(unittest)` here - if anyone depends on your code and\n// doesn't depend on unit-threaded they won't be able to test their own\n// code!\nversion(TestingMyCode) { import unit_threaded; }\nelse                   { enum ShouldFail; }  // so production builds compile\n\nint adder(int i, int j) { return i + j; }\n\n@(\"Test adder\") unittest {\n    adder(2, 3).shouldEqual(5);\n}\n\n@(\"Test adder fails\", ShouldFail) unittest {\n    adder(2, 3).shouldEqual(7);\n}\n```\n\nIf using a custom dub configuration for unit-threaded as shown above, a version\nblock can be used on `Have_unit_threaded` (this is added by dub to the build).\n\nCustom Assertions\n-----------------\nCode speaks louder than words:\n\n```d\n    1.should == 1;\n    1.should.not == 2;\n    1.should in [1, 2, 3];\n    4.should.not in [1, 2, 3];\n\n    void funcThrows() { throw new Exception(\"oops\"); }\n    funcThrows.shouldThrow;\n\n    // or with .be\n    1.should.be == 1;\n    1.should.not.be == 2;\n    1.should.be in [1, 2, 3];\n    4.should.not.be in [1, 2, 3];\n\n    // I know this is operator overload abuse. I still like it.\n    [1, 2, 3].should ~ [3, 2, 1];\n    [1, 2, 3].should.not ~ [1, 2, 2];\n    1.0.should ~ 1.0001;\n    1.0.should.not ~ 2.0;\n```\n\nSee more in the `unit_threaded.should` module.\n\n\nFast compilation mode\n--------------------\nFast compilation mode. Set the version to `unitThreadedLight` and it will\ncompile much faster, but with no error reporting and certain features\nmight not work. Experimental support.\n\n\nAdvanced Usage: Attributes\n--------------------------\n\n`@ShouldFail` is used to decorate a test that is\nexpected to fail, and can be passed a string to explain why.\n`@ShouldFail` should be preferred to `@HiddenTest`. If the\nrelevant bug is fixed or not-yet-implemented functionality is done,\nthe test will then fail, which makes them harder to sweep\nunder the carpet and forget about.\n\nSince code under test might not be thread-safe, the `@Serial`\nattribute can be used on a test. This causes all tests in the same\nmodule that have this attribute to be executed sequentially so they\ndon't interleave with one another.\n\nAlthough not the best practice, it happens sometimes that a test is\nflaky. It is recommended to fix the test, but as a stopgap measure\nthe `@Flaky` UDA can be used to rerun the test up to a default number\nof 10 times. This can be customized by passing it a number\n(e.g. `@Flaky(12)`);\n\nThe `@Name` UDA can be used instead of a plain string in order to name\na `unittest` block.\n\nunit-threaded uses D's package and module system to make it possible\nto select a subset of tests to run. Sometimes however, tests in\ndifferent modules address cross-cutting concerns and it may be\ndesirable to indicate this grouping in order to select only those\ntests. The `@Tags` UDA can be used to do that. Any number of tags\ncan be applied to a test:\n\n```d\n@Tags(\"foo\", \"tagged\")\nunittest { ... }\n```\n\nThe strings a test is tagged with can be used by the test runner\nbinary to constrain which tests to run either by selecting tests\nwith or without tags:\n\n    ./ut @foo ~@bar\n\nThat will run all tests that have the \"foo\" tag that also don't have\nthe \"bar\" tag.\n\n\nThe `@Setup` and `@Shutdown` UDAs can be attached to a\nfree function in a module. If they are, they will be run before/after\neach `unittest` block in a composite (usually a module). This feature\ncurrently only works for `unittest` blocks, not free functions.\nClasses could override `setup` and `shutdown` already.\n\nProperty-based testing\n----------------------\n\nThere is preliminary support for property-based testing.\nTo check a property use the `check` function\nfrom `unit_threaded.property` with a function returning `bool`:\n\n```d\ncheck!((int a) =\u003e a % 2 == 0);\n```\n\nThe above example will obviously fail. By default `check` runs the property\nfunction with 100 random values, pass it a different runtime parameter\nto change that:\n\n```d\ncheck!((int a) =\u003e a % 2 == 0)(10_000); // will still fail\n```\n\nIf using compile-time delegates as above, the types of the input parameters\nmust be explicitly stated. Multiple parameters can be used as long as\neach one is of one of the currently supported types.\n\nMocking\n--------\n\nClasses and interfaces can be mocked like so:\n\n\n```d\ninterface Foo { int foo(int, string); }\nint fun(Foo f, int i, string s) { return f.foo(i * 2, s ~ \"stuff\"); }\n\nauto m = mock!Foo;\nm.expect!\"foo\";\nfun(m, 3, \"bar\");\nm.verify; // throws if not called\n```\n\nTo check the values passed in, pass them to `expect`:\n\n\n```d\nm.expect!\"foo\"(6, \"barstuff\");\nfun(m , 3, \"bar\");\nm.verify;\n```\n\nEither call `expect` then `verify` or call `expectCalled` at the end:\n\n```d\nfun(m, 3, \"bar\");\nm.expectCalled!\"foo\"(6, \"barstuff\");\n```\n\nThe return value is `T.init` unless `returnValue` is called (it's variadic):\n\n```d\nm.returnValue!\"foo\"(2, 3, 4);\nassert(fun(m, 3, \"bar\") == 2);\nassert(fun(m, 3, \"bar\") == 3);\nassert(fun(m, 3, \"bar\") == 4);\nassert(fun(m, 3, \"bar\") == 0);\n```\n\nStructs can also be mocked:\n\n```d\nint fun(T)(T f, int i, string s) { return f.foo(i * 2, s ~ \"stuff\"); }\nauto m = mockStruct(2, 3, 4); // the ints are return values (none need be passed)\nassert(fun(m, 3, \"bar\") == 2);\nm.expectCalled!\"foo\"(6, \"barstuff\");\n```\n\nIf a struct is needed that returns different types for different functions:\n\n```d\n    auto m = mockStruct!(ReturnValues!(\"length\", 5, 3),\n                         ReturnValues!(\"greet\", \"hello\", \"g'day\"));\n    m.length.shouldEqual(5);\n    m.length.shouldEqual(3);\n    m.greet.shouldEqual(\"hello\");\n    m.grett.shouldEqual(\"g'day\");\n```\n\n\nStructs that always throw:\n\n```d\n{\n    auto m = throwStruct;\n    m.foo.shouldThrow!UnitTestException;\n}\n\n{\n    auto m = throwStruct!MyException;\n    m.foo.shouldThrow!MyException;\n}\n\n\n```\n\nCommand-line Parameters\n-----------------------\n\nTo run in single-threaded mode, use `-s`.\n\nThere is support for debug prints in the tests with the `-d` switch.\nTestCases and test functions can print debug output with the\nfunction `writelnUt` available [here](source/unit_threaded/io.d).\n\nTests can be run in random order instead of in threads.  To do so, use\nthe `-r` option.  A seed will be printed so that the same run can be\nrepeated by using the `--seed` option. This implies running in a\nsingle thread.\n\n\nIntegration tests and a sandbox environment\n------------------------------------------\n\nIf you want to write tests that read from and write to the file system,\nyou can use the `Sandbox` struct from\n[`unit_threaded.integration`](subpackages/integration) like so:\n\n```d\nwith(immutable Sandbox()) {\n    writeFile(\"foo.txt\", \"foobarbaz\\ntoto\"); // can also pass string[] for lines\n    shouldExist(\"foo.txt\");\n    shouldNotExist(\"bar.txt\");\n    shouldEqualLines(\"foo.txt\", [\"foobarbaz\", \"toto\"]);\n}\n```\n\nBy default the sandbox main path is `tmp/unit-threaded` but you can change\nthat by calling `Sandbox.setPath`\n\n\nTest Registration and Test Runner\n---------------------------------\n\nThere are two example programs in the [`example`](example/) folder,\none with passing unit tests and the other failing, to show what the\noutput looks like in each case. Because of the way D packages work,\nthey must be run from the top-level directory of the repository.\n\nThe built-in D unittest blocks are included automatically, as seen in\nthe output of both example programs\n(`example.tests.pass_tests.unittest` and its homologue in\n[`example_fail`](example/example_fail.d)). A name will be automatically\ngenerated for them. The user can specify a name by decorating them\nwith a string UDA or the included `@Name` UDA.\n\nThe easiest way to run tests is by doing what the failing example code\ndoes: mixing in `runTestsMain()` in\n[`runner.d`](subpackages/runner/source/unit_threaded/runner/runner.d)\nwith the modules containing the tests as compile-time arguments (as\nstrings).\n\nThere is no need to register tests. The registration is implicit\nand happens with:\n\n* D's `unittest`` blocks\n* Functions with a camelCase name beginning with `test` (e.g. `testFoo()`)\n* Classes that derive from `TestCase` and override `test()`\n\nThe modules to be reflected on must be specified when calling\n`runTests` or `runTestsMain`, but that's usually done as shown in the dub configuration\nabove. Private functions are skipped. `TestCase` also has support for\n`setup()` and `shutdown()`, child classes need only override the\nappropriate functions(s).\n\nTests can be hidden with the `@HiddenTest` attribute. This means\nthat particular test doesn't get run by default but can still be run\nby passing its name as a command-line argument. `HiddenTest` takes\na compile-time string to list the reason why the test is hidden. This\nwould usually be a bug id but can be anything the user wants.\n\nSince D packages are just directories and there the compiler can't\nread the filesystem at compile-time, there is no way to automatically\nadd all tests in a package.  To mitigate this and avoid having to\nmanually write the name of all the modules containing tests,\na dub configuration called `gen_ut_main` runs unit-threaded as\na command-line utility to write the file for you.\n\n\n\nRelated Projects\n----------------\n- [dunit](https://github.com/linkrope/dunit):\n  xUnit Testing Framework for D\n- [DMocks-revived](https://github.com/QAston/DMocks-revived):\n  a mock-object framework that allows to mock interfaces or classes\n- [deject](https://github.com/bgertzfield/deject): automatic dependency injection\n- [specd](https://github.com/jostly/specd):\n  a unit testing framework inspired by [specs2](http://etorreborre.github.io/specs2/) and [ScalaTest](http://www.scalatest.org)\n- [DUnit](https://github.com/kalekold/dunit):\n  a toolkit of test assertions and a template mixin to enable mocking\n","funding_links":[],"categories":["Testing Frameworks"],"sub_categories":["Bare metal / kernel development"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatilaneves%2Funit-threaded","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatilaneves%2Funit-threaded","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatilaneves%2Funit-threaded/lists"}