{"id":23503500,"url":"https://github.com/realyuniquename/hunit","last_synced_at":"2026-02-08T03:07:19.136Z","repository":{"id":57269638,"uuid":"41158688","full_name":"RealyUniqueName/HUnit","owner":"RealyUniqueName","description":"Unit testing framework for Haxe with mocks and stubs.","archived":false,"fork":false,"pushed_at":"2015-12-30T10:24:41.000Z","size":152,"stargazers_count":10,"open_issues_count":2,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-27T16:03:02.325Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haxe","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RealyUniqueName.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":"2015-08-21T13:57:14.000Z","updated_at":"2023-04-16T10:34:11.000Z","dependencies_parsed_at":"2022-09-02T09:51:04.406Z","dependency_job_id":null,"html_url":"https://github.com/RealyUniqueName/HUnit","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FHUnit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FHUnit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FHUnit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RealyUniqueName%2FHUnit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RealyUniqueName","download_url":"https://codeload.github.com/RealyUniqueName/HUnit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252531888,"owners_count":21763295,"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":[],"created_at":"2024-12-25T08:29:59.817Z","updated_at":"2026-02-08T03:07:19.026Z","avatar_url":"https://github.com/RealyUniqueName.png","language":"Haxe","funding_links":[],"categories":[],"sub_categories":[],"readme":"HUnit\n=================\nHUnit - crossplatform unit testing framework for Haxe language with built-in mocking, stubbing and spying.\n\nContents\n-----------------\n* [Why yet another unit testing framework?](#why-yet-another-unit-testing-framework)\n* [Features](#features)\n* [Installation](#installation)\n* [Basic usage](#basic-usage)\n* [Advanced usage](#advanced-usage)\n* [Test cases](#test-cases)\n* [Test reports](#test-reports)\n* [Mocking](#mocking)\n* [Mocking types with @:autoBuild macros](#mocking-types-with-autobuild-macros)\n* [Stubbing](#stubbing)\n* [Verifying method calls](#verifying-method-calls)\n* [Modifying private properties of mocked object](#modifying-private-properties-of-mocked-object)\n* [Validating exceptions](#validating-exceptions)\n* [Assertions](#assertions)\n* [Matchers](#matchers)\n* [Compilation flags](#compilation-flags)\n* [Meta for test methods](#meta-for-test-methods)\n* [Limitations](#limitations)\n\n\nWhy yet another unit testing framework?\n-----------------\nIndeed, there are great frameworks like [utest](http://lib.haxe.org/p/utest/) and [munit](http://lib.haxe.org/p/munit/) already.\nThere is no strong reason for creating another one.\nShort story:\nIn the beginning I was fine with standard `haxe.unit` package. Then with my project growth I need some more functionality,\nwhich I implemented in some minor extensions for `haxe.unit`. At some point I became in need of mocking. [Mockatoo](http://lib.haxe.org/p/mockatoo/)\nwas great, but did not work on all targets, so i implemented simple mocking. And so on, and so forth...\nSuddenly I looked at all that stuff and saw almost all `haxe.unit` code overriden by my 'extensions'.\nSo I decided to separate them into standalone unit testing framework.\n\n\nFeatures\n-----------------\n* Assertions! Wow!\n* Validating exceptions;\n* Mocking, stubbing and verifying calls;\n* Easy test suite setup;\n* Grouping tests, which allows to include/exclude some tests when you want to check some specific things without running other tests.\n* Verbose failure messages;\n* All targets support.\n\n\nInstallation\n-----------------\n```\nhaxelib install hunit\n```\n\n\nBasic usage\n-----------------\nIf you don't need any special configuration for your test suite, your `test.hxml` should look like this:\n```\n#path to your project sources\n-cp src\n#plug HUnit\n-lib hunit\n#path to the directory with test cases\n-D HUNIT_TEST_DIR=tests\n#use HUnit as entry point\n-main HUnit\n#for better stack traces\n-debug\n#project specific setup\n-neko Test.n\n```\nNow all you need is to write your test cases.\n\n\nAdvanced usage\n----------------\nIf you need some configuration before running any tests, you can use your own entry point like this:\n```haxe\nimport hunit.TestSuite;\nimport hunit.TestReport;\n\nclass Test {\n    static public function main () {\n        //perform your setup\n        //\u003c...\u003e\n\n        //instantiate test suite\n        var suite = new hunit.TestSuite();\n        //add all tests in specified directory\n        //this is a macro method, so you can use it on all targets\n        //path should be specified relative to current .hx file\n        suite.addDirectory('unit');\n        //add some tests from other sources\n        suite.add(new some.other.tests.ExampleTest());\n        //start testing\n        suite.run();\n\n        //Finalize your testing. E.g. process test suite report\n        var report : TestReport = suite.report;\n        //\u003c...\u003e\n    }\n}\n```\n\n\nTest cases\n-----------------\nTest case classes should extend `hunit.TestCase`:\n```haxe\nclass SomeFeatureTest extends hunit.TestCase\n{\n    /** Setup environment before the first test in current test case */\n    override public function setupTestCase () : Void { }\n\n    /** Setup new environment before each test */\n    override public function setup () : Void { }\n\n    /** Perform some cleaning after each test */\n    override public function tearDown () : Void { }\n\n    /** Perform some cleaning after last test in this test case */\n    override public function tearDownTestCase () : Void { }\n\n    /** Show `msg` in test suite report without affecting tests results */\n    public function notice (msg:String) : Void { }\n\n    /**\n     * Test methods should start with `test` prefix or should be marked with `@test` meta\n     */\n    public function testSomeStuff()\n    {\n        var expected = 1;\n        var actual   = 2 - 1;\n        assert.equal(expected, actual, '2-1 and 1 should be equal');\n    }\n\n    @test\n    public function someOtherStuff()\n    {\n        var expected = 'hello';\n        var actual   = 'hell' + 'o';\n        assert.equal(expected, actual);\n    }\n}\n```\n\n\nTest reports\n-----------------\nYou can implement `hunit.IReportWriter` and pass it to `hunit.TestSuite` constructor to be able to create reports in xml\nor json or to do whatever you want with tests results.\n\nMocking\n-----------------\nRight now mocking is only supported for classes and interfaces.\nAbstracts and typedefs are candidates to be implemented.\nLet's imagine we want to mock following class:\n```haxe\nclass MyClass\u003cT\u003e {\n    public var item : T;\n    public function new (initial:T)\n    {\n        item = initial;\n    }\n\n    public function changeValue(newValue:T) : T\n    {\n        item = newValue;\n        return newValue;\n    }\n}\n```\nNow, to mock it we use `mock()`  method of `hunit.TestCase`:\n```haxe\nclass MyTest extends hunit.TestCase\n{\n    public function testMocking ()\n    {\n        //create mock without invoking original constructor. Pass list of type parameters as an array to second argument\n        var m = mock(MyClass, [String]).get();\n        //constructor was not invoked, thus `m.item` should not be initialized\n        assert.isNull(m.item);\n\n        //create mock using original constructor\n        var m = mock(MyClass, [String]).create('Hello, world');\n        //since we invoked constructor, `m.item` should be set\n        assert.equal('Hello, world', m.item);\n\n        //ensure we created `MyClass` instance\n        assert.type(MyClass, m);\n        //ensure we created a mock\n        assert.type(hunit.mock.IMock, m);\n    }\n}\n```\n\nMocking types with @:autoBuild macros\n-----------------\nIf you are trying to mock some type which has `@:autoBuild` macros you can experience random bugs and unexpected behavior.\nTo avoid such issues check for `@:mock` meta in your macros and skip type building for types whith this meta.\n\n\nStubbing\n-----------------\nSo you want to stub some methods? Easy!\n```haxe\npublic function testStubbing ()\n{\n    var m = mock(MyClass, [String]).create('Hello');\n    //`m.item` is set to 'Hello'\n    assert.equal('Hello', m.item);\n\n    //stub method `changeValue` of `m` instance\n    stub(m).changeValue();\n    m.changeValue('some other stuff');\n    //still 'Hello'\n    assert.equal(item, m.item);\n\n    //want your stub to return predefined value? Here you are\n    stub(m).changeValue().returns('World');\n    var actual = m.changeValue('some other stuff');\n    assert.equal('World', actual);\n    assert.equal('Hello', m.item);\n\n    //want your stub to throw an exception?\n    stub(m).changeValue().throws('Terrible error');\n    try {\n        m.changeValue('oops');\n    } catch (e:Dynamic) {\n        assert.equal('Terrible error', e);\n        assert.equal('Hello', m.item);\n    }\n\n    //need different behavior for different argument values?\n    stub(m).changeValue('Hello').returns('World');\n    stub(m).changeValue('oops').throws('Terrible error');\n\n    //implement custom behavior\n    sutb(m).changeValue().implement(function (item:String) {\n        //do some crazy stuff\n    });\n\n    //stub all methods by default\n    var m = mock(MyClass, [String]).stubAll().create('Hello');\n    //but one method should use original behavior\n    expect(m).changeValue().unstub();\n}\n```\nYou can pass matchers instead of exact values to stubbed method arguments. Read below for more on matchers.\nBy default stubbed methods return `null` or type specific default value which is `0` for `Int` and `Float` and `false` for `Bool`.\n\n\nVerifying method calls\n-----------------\nIf you need to ensure your tested unit calls some methods, you can use `expect()` of `hunit.TestCase`\n```haxe\npublic function testInvocation ()\n{\n    var m = mock(MyClass, [String]).create('Hello');\n    //Or if you want your test to fail if any method except expected one is invoked, add `strict()`:\n    var m = mock(MyClass, [String]).strict().create('Hello');\n\n    //test will fail if `changeValue()` method of `m` will not be executed with 'World' argument\n    expect(m).changeValue('World');\n\n    //fail if `changeValue()` will not return 'World'\n    expect(m).changeValue().returns('World');\n\n    //fail if `changeValue()` will not throw specified exception\n    expect(m).changeValue().throws('Terrible error');\n\n    //fail if `changeValue()` will be called less than two times\n    expect(m).changeValue().atLeast(2);\n\n    //or fail if combination of above expectations will not be satisfied\n    expect(m).changeValue('World').returns('World').atLeast(2);\n\n    //you can also expect invocations of stubbed methods\n    stub(m).changeValue('World').returns('World').exactly(2);\n\n    var testedUnit = function () {\n        m.changeValue('World');\n    }\n}\n```\nYou can pass matchers instead of exact values to expected arguments, return values or exception. Read below for more on matchers.\nSpecify the amount of expected calls using these methods:\n\n* `any()` (default) Never fail because of invocations count;\n* `once()` Test passes if method will be called one time only;\n* `never()` Test passes if method will be never called;\n* `atLeast(amount)` Test passes if method will be called at least `amount` times;\n* `exactly(amount)` Test passes if method will be called exactly `amount` times.\n\n\nModifying private properties of mocked object\n-----------------\nYou can get access to private properties of mocked objects with `TestCase.modify()` method like this:\n```haxe\nclass Dummy\n{\n    private var privateField : String;\n    public function new () {}\n    public function getPrivateField() return privateField;\n}\n\nclass DummyTest extends hunit.TestCase\n{\n    public function testPrivateAccess ()\n    {\n        var dummy = mock(Dummy).create();\n        modify(dummy).privateField = 'hello';\n        assert.equal('hello', dummy.getPrivateField());\n    }\n}\n```\n\n\nValidating exceptions\n-----------------\nIf you want to be sure some unit throws exception.\n```\npublic function testMethodThrowsException ()\n{\n    expectException('Terrible error');\n\n    var testedUnit = function () throw 'Terrible error';\n\n    testedUnit();\n}\n```\nThis test will pass only if 'Terrible error' will be raised.\nInstead of exact value you can pass matchers to `expectException()`.  Read below for more on matchers.\n\n\nAssertions\n-----------------\nThese are implemented assertions, which you can invoke on `assert` property of `hunit.TestCase`.\nUse `message` argument to print custom message if assertion fails.\nDon't pass `pos` argument unless you know what you're doing.\n```haxe\n/** Validate `value` against matcher. More on matchers below. */\nassert.match\u003cT\u003e (match:Match\u003cT\u003e, value:T, message:String = null, ?pos:PosInfos);\n\n/**\n * Success if `expected` and `actual` are equal.\n * Compares enums with `Type.enumEq()`, callbacks with `Reflect.compareMethods()` and everything else with `==`.\n */\nassert.equal\u003cT\u003e (expected:T, actual:T, message:String = null, ?pos:PosInfos);\n\n/** Success if `expected` and `actual` are not equal */\nassert.notEqual\u003cT\u003e (expected:T, actual:T, message:String = null, ?pos:PosInfos);\n\n/** Success if type of `value` is of `expectedType` */\nassert.type (expectedType:Class\u003cDynamic\u003e, value:Dynamic, message:String = null, ?pos:PosInfos);\n\n/** Success if `value` is null */\nassert.isNull (value:Dynamic, message:String = null, ?pos:PosInfos);\n\n/** Success if `value` is not null */\nassert.notNull (value:Dynamic, message:String = null, ?pos:PosInfos);\n\n/** Success if `value` is `true` */\nassert.isTrue (value:Bool, message:String = null, ?pos:PosInfos);\n\n/** Success if `value` is `false` */\nassert.isFalse (value:Bool, message:String = null, ?pos:PosInfos);\n\n/** Success if `pattern` regexp match `value`*/\nassert.regexp (pattern:EReg, value:String, message:String = null, ?pos:PosInfos);\n\n/**\n * Success if `expected` and `actual` are similar objects/arrays/maps.\n *\n * Asserting with objects:\n * All fields of `expected` object must have corresponding fields in `actual` object to pass this assertion.\n * Object `actual` is allowed to have fields, which do not exist in `expected` object.\n * It's not necessary for `expected` and `actual` to be instances of the same type.\n * Fields values of `expected` object can be matchers.\n *\n * Asserting with arrays:\n * To pass this assertion `actual` and `expected` arrays must be of the same length and\n * have corresponding elements match each other.\n * Elements of `expected` array can be matchers.\n *\n * Asserting with maps:\n * `expected` and `actual` maps must have the same set of keys and their corresponding values must match each other.\n * Values of `expected` map can be matchers.\n */\nassert.similar (expected:Dynamic, actual:Dynamic, message:String = null, ?pos:PosInfos);\n\n/** Force test failure */\nassert.fail (message:String = null, ?pos:PosInfos);\n\n/** Add warning to test report */\nassert.warn (message:String = null, ?pos:PosInfos);\n\n/** Mark test as successful if there are no other assertions in test */\nassert.success (?pos:PosInfos);\n```\n\n\nMatchers\n-----------------\nMatchers are used to check if verified values match expected values.\nThey are available as methods of `match` property of `hunit.TestCase`.\n\nYou can use matchers as arguments for stubbed or expected method calls, expected method result and expected exceptions:\n```haxe\nvar m = mock(MyClass, [String]).get();\n\n//match any string argument\nexpect(m).changeValue(match.type(String));\n//match any string which match specified regular expression\nstub(m).changeValue(match.regexp(~/ello$/i));\n\n//expect method to return a value which is not equal to 'World'\nexpect(m).changeValue().returns(match.notEqual('World'));\n\n//expect method to throw any exception\nexpect(m).changeValue().throws(match.any());\n\n//expect raising object exception with field `message` which is equal with 'Terrible error' and `code` field not equal with `10`\nexpectException(match.similar(\n    {\n        message : 'Terrible error',\n        code    : match.notEqual(10)\n    }\n));\n\n//you can also chain matchers so that verified value will match only if all matchers are satisfied\n//Chain of matchers is processed \"as is\" without any priority.\nexpect(m).changeValue( match.regexp(~/ello/i).and.notEqual('Hello').or.equal('World') );\n```\nHere is a list of implemented matchers:\n```haxe\n/** Match any value */\nmatch.any ();\n\n/** Match values of specified `type` */\nmatch.type\u003cT\u003e (type:Class\u003cT\u003e);\n\n/** Match strings which match `pattern` */\nmatch.regexp (pattern:EReg);\n\n/** Match objects whose fields values match corresponding fields values of `pattern`. */\nmatch.similar (pattern:Dynamic);\n\n/** Match values which are equal to `value` */\nmatch.equal\u003cT\u003e (value:T);\n\n/** Match values which are not equal to `value` */\nmatch.notEqual\u003cT\u003e (value:T);\n\n/** Match if `verify()` returns `true` when invoked against verified value */\nmatch.callback\u003cT\u003e (verify:T-\u003eBool);\n```\n\n\nCompilation flags\n-----------------\n* `-main HUnit`\nIf your test suite does not need any special configuration, you can use `HUnit` as main class for test suite.\n* `-D HUNIT_TEST_DIR=path/to/dir`\nAdds all tests in path/to/dir to test suite if combined with `-main HUnit`.\nPath should be specified relative to current working directory from which `haxe` compiler is executed.\n* `-D HUNIT_EXCLUDE=some.tests,some.SingleTest,\u003c...\u003e`\nExclude specified packages and/or classes from test suite\n* `-D HUNIT_GROUP=group1,group2,\u003c...\u003e`\nRun tests assigned to specified groups only. Tests can be assigned to some groups by adding meta `@group(group1,group4,group8)` to test methods.\n* `-D HUNIT_EXCLUDE_GROUP=group1,group2,\u003c...\u003e`\nDo not run tests assigned to specified groups.\n\n\nMeta for test methods\n---------------------\n* `@test`\nMethod is considered to be a test if marked with this meta.\n* `@group('group1', 'group2', \u003c...\u003e)`\nAssign test to specified groups. If this meta is set for test case class, then all tests in that case will be assigned to specified groups.\n* `@incomplete('Because something is not implemented')`\nMark test as incomplete. This meta will add warning to test report.\n* `@depends('testAnotherThing', 'testDifferentThing')`\nIf `testAnotherThing` fails or `testDifferentThig` fails, then test with this meta will be skipped. All these tests must be in one TestCase.\n* `@inheritTests('testSomeThing', 'testAnotherThing')`\nIf `MyTest` extends `AnotherTest`, then this meta copies specified test methods from `AnotherTest` to `MyTest`. If no test names provided, then all tests will be copied.\n\n\nLimitations\n-----------\nIt's not allowed to stub or expect `toString()` methods. HXCPP does not allow methods named `toString()` to return values\nother than strings, while HUnit needs to return another type to chain configuration methods for stubs and expects.\nIf you really need to operate `toString()` create another method like `asString()` and use it.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealyuniquename%2Fhunit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frealyuniquename%2Fhunit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frealyuniquename%2Fhunit/lists"}