source/rooibos/BaseTestSuite.bs

import "pkg:/source/rooibos/Matchers.bs"
import "pkg:/source/rooibos/TestGroup.bs"
import "pkg:/source/rooibos/Utils.bs"
import "pkg:/source/roku_modules/rooibos_promises/promises.brs"

namespace rooibos
    class BaseTestSuite

        'test state
        protected name = "BaseTestSuite" 'set the name to the name of your test
        protected filePath = invalid
        protected pkgPath = invalid
        protected isValid = false
        protected hasSoloTests = false
        protected hasIgnoredTests = false
        protected isSolo = false
        protected isIgnored = false
        protected noCatch = false
        protected isNodeTest = false
        protected nodeName = invalid
        protected lineNumber = 1
        protected groups = []
        protected groupsData = []
        protected stats = invalid
        protected currentAssertLineNumber = -1
        protected valid = false
        protected hasFailures = false
        protected hasSoloGroups = false
        protected isFailingFast = false
        protected stubs = invalid
        protected mocks = invalid
        protected __stubId = -1
        protected __mockId = -1
        protected __mockTargetId = -1
        protected currentExecutionTime = 0
        protected timedOut = false
        public deferred = invalid

        'special values
        protected invalidValue = "#ROIBOS#INVALID_VALUE" ' special value used in mock arguments
        protected ignoreValue = "#ROIBOS#IGNORE_VALUE" ' special value used in mock arguments

        'built in any matchers
        protected anyStringMatcher = { "matcher": rooibos.Matcher.anyString }
        protected anyBoolMatcher = { "matcher": rooibos.Matcher.anyBool }
        protected anyNumberMatcher = { "matcher": rooibos.Matcher.anyNumber }
        protected anyAAMatcher = { "matcher": rooibos.Matcher.anyAA }
        protected anyArrayMatcher = { "matcher": rooibos.Matcher.anyArray }
        protected anyNodeMatcher = { "matcher": rooibos.Matcher.anyNode }
        protected allowNonExistingMethodsOnMocks = true
        protected isAutoAssertingMocks = true

        protected currentResult = invalid

        protected global = invalid
        protected catchCrashes = false
        protected throwOnFailedAssertion = false

        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ base methods to override
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        function new()
            data = m.getTestSuiteData()
            if data = invalid then
                rooibos.common.logError("ERROR RETRIEVING TEST SUITE DATA!! this is a rooibos BUG - please report the suite that resulted in a corrupt test. Thanks")
            else
                m.name = data.name
                m.filePath = data.filePath
                m.pkgPath = data.pkgPath
                m.valid = data.valid
                m.hasFailures = data.hasFailures
                m.hasSoloTests = data.hasSoloTests
                m.hasIgnoredTests = data.hasIgnoredTests
                m.hasSoloGroups = data.hasSoloGroups
                m.isSolo = data.isSolo
                m.isIgnored = data.isIgnored
                m.isAsync = data.isAsync
                m.asyncTimeout = data.asyncTimeout
                m.noCatch = data.noCatch
                m.groupsData = data.testGroups
                m.lineNumber = data.lineNumber
                m.isNodeTest = data.isNodeTest
                m.nodeName = data.nodeName
                m.generatedNodeName = data.generatedNodeName
                m.isFailingFast = false

                if m.isNodeTest then
                    m.deferred = rooibos.promises.create()
                end if

                m.stats = new rooibos.Stats()
            end if
        end function

        ' @ignore
        function getTestSuiteData()
            'this will be injected by the plugin
        end function

        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ used for entire suite - use annotations to use elsewhere
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        protected function setup()
        end function

        protected function tearDown()
        end function

        protected function beforeEach()
        end function

        protected function afterEach()
        end function

        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ running
        '++++++++++++++++++++++++++++++++++++?+++++++++++++++++++++++++

        ' @ignore
        function run()
            m.notifyReportersOnSuiteBegin()
            rooibos.common.logTrace(">>>>>>>>>>>>")
            rooibos.common.logTrace("RUNNING TEST SUITE")
            if m.isNodeTest = true then
                rooibos.common.logTrace("THIS GROUP IS ASYNC")
                m.runAsync()
            else
                rooibos.common.logTrace("THIS GROUP IS SYNC")
                m.runSync()
            end if
        end function

        ' @ignore
        function runSync()
            for each groupData in m.groupsData
                group = new TestGroup(m, groupData)
                m.groups.push(group)
                group.run()
                m.stats.merge(group.stats)

                if m.stats.hasFailures and m.isFailingFast = true then
                    rooibos.common.logDebug("Terminating suite due to failed group")
                    exit for
                end if

            end for
            m.notifyReportersOnSuiteComplete()
        end function

        ' @ignore
        function runASync()
            rooibos.common.logTrace("Running groups async")

            m.groups = []
            for each groupData in m.groupsData
                group = new TestGroup(m, groupData)
                m.groups.push(group)
            end for

            m.currentGroupIndex = -1
            m.runNextAsync()

        end function

        ' @ignore
        private function runNextAsync()
            rooibos.common.logTrace("Getting next async group")
            m.currentGroupIndex++
            m.currentGroup = m.groups[m.currentGroupIndex]
            if m.currentGroup = invalid then
                rooibos.common.logTrace("All groups are finished")
                'finished
                m.testSuiteDone()
            else
                group = m.currentGroup
                group.run()
                if rooibos.promises.isPromise(group.deferred) then
                    rooibos.promises.onFinally(group.deferred, function(context as roAssociativeArray)
                        context.self.onAsyncGroupComplete(context.group)
                    end function, { group: group, self: m })
                else
                    m.onAsyncGroupComplete(group)
                end if
            end if
        end function

        ' @ignore
        private function onAsyncGroupComplete(group = invalid as rooibos.TestGroup) as void
            rooibos.common.logTrace("++ CURRENT GROUP COMPLETED")

            group = group = invalid ? m.currentGroup : group
            if group = invalid then
                rooibos.common.logError("Cannot find test group to mark async finished for?!")
                return
            end if
            m.stats.merge(group.stats)

            if m.stats.hasFailures and m.isFailingFast then
                rooibos.common.logTrace("Terminating group due to failed test")
                m.isTestFailedDueToEarlyExit = true
                m.testSuiteDone()
            else
                m.runNextAsync()
            end if
        end function

        ' calculate if the suite has timed out. Will return true if the suite flipped the timedOut flag
        ' @ignore
        function isSuiteTimedOut() as boolean
            if m.isNodeTest and m.asyncTimeout > 0 and m.currentExecutionTime >= m.asyncTimeout then
                m.timedOut = true
                return true
            end if
            return false
        end function

        ' @ignore
        function runTest(test as rooibos.Test) as dynamic
            m.currentResult = test.result
            if test.isIgnored then
                m.currentResult.skip("Test is ignored")
                return invalid
            end if

            ' Fail the test if the suite has timed out.
            ' Currently, this will only happen if the suite is a node test.
            if m.isNodeTest and m.timedOut then
                m.currentResult.fail("Suite test execution exceeded " + m.asyncTimeout.toStr() + "ms")
                return invalid
            end if

            m.currentAssertLineNumber = -1
            m.currentResult.throwOnFailedAssertion = m.throwOnFailedAssertion
            if m.catchCrashes and not test.noCatch and not m.noCatch then
                try
                    test.run()
                    if m.isAutoAssertingMocks = true and test.deferred = invalid then
                        m.AssertMocks()
                        m.CleanMocks()
                        m.CleanStubs()
                    end if
                catch error
                    m.currentResult.crash("test crashed!", error)
                    if rooibos.promises.isPromise(test.deferred) then
                        rooibos.promises.reject(error, test.deferred)
                    end if
                end try
            else
                test.run()
                if m.isAutoAssertingMocks = true and test.deferred = invalid then
                    m.AssertMocks()
                    m.CleanMocks()
                    m.CleanStubs()
                end if
            end if

            return test.deferred
        end function

        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ Assertions
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        ' Fail immediately, with the given message
        ' @param {String} [msg=""] - message to display in the test report
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function fail(msg = "Error" as string, actual = "" as string, expected = "" as string, createError = false as boolean) as boolean
            if m.currentResult.isFail then
                if m.throwOnFailedAssertion then
                    throw m.currentResult.getMessage()
                end if
                return false
            end if

            error = invalid
            if createError then
                try
                    throw msg
                catch error
                end try
            end if

            m.currentResult.fail(msg, m.currentAssertLineNumber, actual, expected, error)
            return false
        end function

        ' Fail immediately, with the given message
        ' @ignore
        ' @param {String} [msg=""] - message to display in the test report
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function skip(msg = "Skipped" as string) as boolean
            if m.currentResult.isFail then
                if m.throwOnFailedAssertion then
                    throw m.currentResult.getMessage()
                end if
                return false
            end if
            m.currentResult.skip(msg)
            return false
        end function

        ' Fail immediately, with the given exception
        ' @param {Dynamic} [error] - exception to fail on
        ' @param {String} [msg=""] - message to display in the test report
        ' @returns {Boolean} true if failure was set, false if the test is already failed
        function failCrash(error as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                if m.throwOnFailedAssertion then
                    throw m.currentResult.getMessage()
                end if
                return false
            end if
            if msg = "" then
                msg = error.message
            end if
            m.currentResult.fail(msg, m.currentAssertLineNumber)
            m.currentResult.crash(msg, error)
            return true
        end function

        function failBecauseOfTimeOut() as boolean
            if m.currentResult.isFail then
                return false
            end if
            m.currentResult.fail("Async test execution exceeded " + m.currentTimeout.toStr() + "ms")
            m.done()
            return false
        end function

        ' Fail the test if the expression is true.
        ' @param {Dynamic} expr - An expression to evaluate.
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertFalse(expr as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if not rooibos.common.isBoolean(expr) or expr then
                    actual = rooibos.common.asMultilineString(expr, true)
                    expected = rooibos.common.asMultilineString(false, true)
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(actual)}" to be ${rooibos.common.truncateString(expected)}`
                    end if
                    return m.fail(msg, actual, expected, true)
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail the test unless the expression is true.
        ' @param {Dynamic} expr - An expression to evaluate.
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertTrue(expr as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if not rooibos.common.isBoolean(expr) or not expr then
                    actual = rooibos.common.asMultilineString(expr, true)
                    expected = rooibos.common.asMultilineString(true, true)
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(actual)}" to be ${rooibos.common.truncateString(expected)}`
                    end if
                    return m.fail(msg, actual, expected, true)
                end if

                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the two objects are unequal as determined by the '<>' operator.
        ' @param {Dynamic} first - first object to compare
        ' @param {Dynamic} second - second object to compare
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertEqual(first as dynamic, second as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if not rooibos.common.eqValues(first, second) then
                    actual = rooibos.common.asMultilineString(first, true)
                    expected = rooibos.common.asMultilineString(second, true)
                    if msg = "" then
                        messageActual = rooibos.common.truncateString(actual)
                        messageExpected = rooibos.common.truncateString(expected)
                        msg = `expected "${messageActual}" to equal "${messageExpected}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' does a fuzzy comparison
        ' @param {Dynamic} first - first object to compare
        ' @param {Dynamic} second - second object to compare
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertLike(first as dynamic, second as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if not rooibos.common.eqValues(first, second, true) then
                    actual = rooibos.common.asMultilineString(first, true)
                    expected = rooibos.common.asMultilineString(second, true)
                    if msg = "" then
                        messageActual = rooibos.common.truncateString(actual)
                        messageExpected = rooibos.common.truncateString(expected)
                        msg = `expected "${messageActual}" to be like "${messageExpected}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the two objects are equal as determined by the '=' operator.
        ' @param {Dynamic} first - first object to compare
        ' @param {Dynamic} second - second object to compare
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNotEqual(first as dynamic, second as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if rooibos.common.eqValues(first, second) then
                    actual = rooibos.common.asMultilineString(first, true)
                    expected = rooibos.common.asMultilineString(second, true)
                    if msg = "" then
                        messageActual = rooibos.common.truncateString(actual)
                        messageExpected = rooibos.common.truncateString(expected)
                        msg = `expected "${messageActual}" to not equal "${messageExpected}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the value is not invalid.
        ' @param {Dynamic} value - value to check - value to check for
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertInvalid(value as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if rooibos.common.getSafeType(value) <> "Invalid" then
                    if msg = "" then
                        actual = rooibos.common.asMultilineString(value, true)
                        msg = `expected "${rooibos.common.truncateString(actual)}" to be invalid`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the value is invalid.
        ' @param {Dynamic} value - value to check - value to check for
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNotInvalid(value as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if rooibos.common.getSafeType(value) = "Invalid" then
                    if msg = "" then
                        actual = rooibos.common.asMultilineString(value, true)
                        msg = `expected "${rooibos.common.truncateString(actual)}" to not be invalid`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the aa doesn't have the key.
        ' @param {Dynamic} aa - target aa
        ' @param {String} key - key name
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertAAHasKey(aa as dynamic, key as string, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if not rooibos.common.isAssociativeArray(aa) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not aa.ifAssociativeArray.DoesExist(key) then
                    if msg = "" then
                        actual = rooibos.common.asMultilineString(aa, true)
                        msg = `expected "${rooibos.common.truncateString(actual)}" to have property "${key}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the aa has the key.
        ' @param {Dynamic} aa - target aa
        ' @param {String} key - key name
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertAANotHasKey(aa as dynamic, key as string, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(aa) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if aa.ifAssociativeArray.DoesExist(key) then
                    if msg = "" then
                        actual = rooibos.common.asMultilineString(aa, true)
                        msg = `expected "${rooibos.common.truncateString(actual)}" to not have property "${key}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the aa doesn't have the keys list.
        ' @param {Dynamic} aa - A target associative array.
        ' @param {Dynamic} keys - Array of key names.
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertAAHasKeys(aa as dynamic, keys as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(aa) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isArray(keys) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(keys, true))}" to be an Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                foundKeys = []
                missingKeys = []
                for each key in keys
                    if not aa.ifAssociativeArray.DoesExist(key) then
                        missingKeys.push(key)
                    else
                        foundKeys.push(key)
                    end if
                end for

                if missingKeys.count() > 0 then
                    actual = rooibos.common.asMultilineString(foundKeys, true)
                    expected = rooibos.common.asMultilineString(keys, true)
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to have properties ${rooibos.common.truncateString(missingKeys.join(", "))}`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the aa has the keys list.
        ' @param {Dynamic} aa - A target associative array.
        ' @param {Dynamic} keys - Array of key names.
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertAANotHasKeys(aa as dynamic, keys as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(aa) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isArray(keys) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(keys, true))}" to be an Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                foundKeys = []
                for each key in keys
                    if aa.ifAssociativeArray.DoesExist(key) then
                        foundKeys.push(formatJson(key))
                    end if
                end for

                if foundKeys.count() > 0 then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(aa, true))}" to not have properties ${rooibos.common.truncateString(foundKeys.join(", "))}`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array doesn't have the item.
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} value - value to check - value to check for
        ' @param {Dynamic} key - key name in associative array
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayContains(array as dynamic, value as dynamic, key = invalid as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.arrayContains(array, value, key) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array does not contain all of the aa's in the values array.
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} values - array of aas to look for in target array
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayContainsAAs(array as dynamic, values as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isArray(values) then
                    if msg = "" then
                        msg = `expected value "${rooibos.common.truncateString(rooibos.common.asMultilineString(values, true))}" must be an Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `actual value "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" must be an Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                for each value in values
                    isMatched = false
                    if not rooibos.common.isAssociativeArray(value) then
                        if msg = "" then
                            msg = `expected search value "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}" to be an AssociativeArray`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                    for each item in array
                        if rooibos.common.IsAssociativeArray(item) then
                            isValueMatched = true
                            for each key in value
                                fieldValue = value[key]
                                itemValue = item[key]
                                if not rooibos.common.eqValues(fieldValue, itemValue) then
                                    isValueMatched = false
                                    exit for
                                end if
                            end for
                            if isValueMatched then
                                isMatched = true
                                exit for
                            end if
                        end if
                    end for ' items in array

                    if not isMatched then
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if

                end for 'values to match
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array has the item.
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} value - value to check - Value to check for
        ' @param {Dynamic} key - A key name for associative array.
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayNotContains(array as dynamic, value as dynamic, key = invalid as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.arrayContains(array, value, key) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to not contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array doesn't have the item subset.
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} subset - items to check presence of
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `expected target "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isAssociativeArray(subset) and not rooibos.common.isArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isAssociativeArray(array) and not rooibos.common.isAssociativeArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isArray(array) and not rooibos.common.isArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an Array to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                isAA = rooibos.common.isAssociativeArray(subset)
                for each item in subset
                    key = invalid
                    value = item
                    if isAA then
                        key = item
                        value = subset[key]
                    end if
                    if not rooibos.common.arrayContains(array, value, key) then
                        if msg = "" then
                            if isAA then
                                msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain property "${rooibos.common.truncateString(key)}" with value "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"`
                            else
                                msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"`
                            end if
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                end for
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array have the item from subset.
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} subset - items to check presence of
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayNotContainsSubset(array as dynamic, subset as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `expected target "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isAssociativeArray(subset) and not rooibos.common.isArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isAssociativeArray(array) and not rooibos.common.isAssociativeArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isArray(array) and not rooibos.common.isArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an Array to match type "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                isAA = rooibos.common.isAssociativeArray(subset)
                for each item in subset
                    key = invalid
                    value = item
                    if isAA then
                        key = item
                        value = subset[key]
                    end if
                    if rooibos.common.arrayContains(array, value, key) then
                        if msg = "" then
                            if isAA then
                                msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to not contain property "${rooibos.common.truncateString(key)}" with value "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"`
                            else
                                msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to not contain "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}"`
                            end if
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                end for
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array items count <> expected count
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} count - An expected array items count
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayCount(array as dynamic, count as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isNumber(count) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(count, true))}" to be an Number`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isAssociativeArray(array) then
                    actualCount = array.ifAssociativeArray.count()
                else
                    actualCount = array.count()
                end if

                if actualCount <> count then
                    if msg = "" then
                        msg = `expected count "${actualCount}" to be "${count}"`
                    end if
                    m.fail(msg, rooibos.common.asMultilineString(actualCount, true), rooibos.common.asMultilineString(count, true), true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array items count = expected count.
        ' @param {Dynamic} array - target array
        ' @param {Dynamic} count - An expected array items count.
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayNotCount(array as dynamic, count as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isNumber(count) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(count, true))}" to be an Number`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isAssociativeArray(array) then
                    actualCount = array.ifAssociativeArray.count()
                else
                    actualCount = array.count()
                end if

                if actualCount = count then
                    if msg = "" then
                        msg = `expected count "${actualCount}" to not be "${count}"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the item is not empty array or string.
        ' @param {Dynamic} item - item to check
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertEmpty(item as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if rooibos.common.isAssociativeArray(item) then
                    if not item.isEmpty() then
                        actual = rooibos.common.asMultilineString(item, true)
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(actual)}" to be empty`
                        end if
                        m.fail(msg, actual, rooibos.common.asMultilineString({}, true), true)
                        return false
                    end if
                else if rooibos.common.isArray(item) then
                    if not item.isEmpty() then
                        actual = rooibos.common.asMultilineString(item, true)
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(actual)}" to be empty`
                        end if
                        m.fail(msg, actual, rooibos.common.asMultilineString([], true), true)
                        return false
                    end if
                else if rooibos.common.isString(item) then
                    if not item.isEmpty() then
                        actual = rooibos.common.asMultilineString(item, true)
                        if msg = "" then
                            msg = `expected ${rooibos.common.truncateString(actual)} to be empty`
                        end if
                        m.fail(msg, actual, "", true)
                        return false
                    end if
                else
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be an AssociativeArray, Array, or String`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the item is empty array or string.
        ' @param {Dynamic} item - item to check
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNotEmpty(item as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if rooibos.common.isAssociativeArray(item) then
                    if item.isEmpty() then
                        if msg = "" then
                            actual = rooibos.common.asMultilineString(item, true)
                            msg = `expected "${rooibos.common.truncateString(actual)}" to not be empty`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                else if rooibos.common.isArray(item) then
                    if item.isEmpty() then
                        if msg = "" then
                            actual = rooibos.common.asMultilineString(item, true)
                            msg = `expected "${rooibos.common.truncateString(actual)}" to not be empty`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                else if rooibos.common.isString(item) then
                    if item.isEmpty() then
                        if msg = "" then
                            actual = rooibos.common.asMultilineString(item, true)
                            msg = `expected ${rooibos.common.truncateString(actual)} to be empty`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                else
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be an AssociativeArray, Array, or String`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the array doesn't contains items of specific type only.
        ' @param {Dynamic} array - target array
        ' @param {String} typeStr - type name - must be String, Array, Boolean, or AssociativeArray
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertArrayContainsOnlyValuesOfType(array as dynamic, typeStr as string, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if typeStr <> "String" and typeStr <> "Integer" and typeStr <> "Boolean" and typeStr <> "Array" and typeStr <> "AssociativeArray" then
                    if msg = "" then
                        msg = `expect type ${rooibos.common.asMultilineString(typeStr, true)} to be "Boolean", "String", "Integer", "Array", or "AssociativeArray"`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isAssociativeArray(array) and not rooibos.common.isArray(array) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray or Array`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                isAA = rooibos.common.isAssociativeArray(array)
                methodName = "Rooibos_Common_Is" + typeStr
                typeCheckFunction = m.getIsTypeFunction(methodName)
                if typeCheckFunction <> invalid then
                    for each item in array
                        key = invalid
                        if isAA then
                            key = item
                            item = array[key]
                        end if
                        if not typeCheckFunction(item) then
                            if msg = "" then
                                if isAA then
                                    msg = `expected "${rooibos.common.truncateString(key)}: ${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be type ${rooibos.common.asMultilineString(typeStr, true)}`
                                else
                                    msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(item, true))}" to be type ${rooibos.common.asMultilineString(typeStr, true)}`
                                end if
                            end if
                            m.fail(msg, "", "", true)
                            return false
                        end if
                    end for
                else
                    ' I think we can remove this check, as we are already checking for valid types?
                    ' Will revisit this later.
                    throw `could not find comparator for type ${rooibos.common.asMultilineString(typeStr, true)}`
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' @ignore
        function getIsTypeFunction(name as string) as dynamic
            if name = "Rooibos_Common_IsFunction" then
                return rooibos.common.isFunction
            else if name = "Rooibos_Common_IsXmlElement" then
                return rooibos.common.isXmlElement
            else if name = "Rooibos_Common_IsInteger" then
                return rooibos.common.isInteger
            else if name = "Rooibos_Common_IsBoolean" then
                return rooibos.common.isBoolean
            else if name = "Rooibos_Common_IsFloat" then
                return rooibos.common.isFloat
            else if name = "Rooibos_Common_IsDouble" then
                return rooibos.common.isDouble
            else if name = "Rooibos_Common_IsLongInteger" then
                return rooibos.common.isLongInteger
            else if name = "Rooibos_Common_IsNumber" then
                return rooibos.common.isNumber
            else if name = "Rooibos_Common_IsList" then
                return rooibos.common.isList
            else if name = "Rooibos_Common_IsArray" then
                return rooibos.common.isArray
            else if name = "Rooibos_Common_IsAssociativeArray" then
                return rooibos.common.isAssociativeArray
            else if name = "Rooibos_Common_IsSGNode" then
                return rooibos.common.isSGNode
            else if name = "Rooibos_Common_IsString" then
                return rooibos.common.isString
            else if name = "Rooibos_Common_IsDateTime" then
                return rooibos.common.isDateTime
            else if name = "Rooibos_Common_IsUndefined" then
                return rooibos.common.isUndefined
            else
                return invalid
            end if
        end function

        ' Asserts that the value is a node of designated type
        ' @param {Dynamic} value - value to check - target node
        ' @param {String} typeStr - type name
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertType(value as dynamic, typeStr as string, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(value) <> typeStr then
                    actual = rooibos.common.asMultilineString(type(value), true)
                    expected = rooibos.common.asMultilineString(typeStr, true)
                    if msg = "" then
                        msg = `expected ${rooibos.common.truncateString(actual)} to be type ${rooibos.common.truncateString(expected)}`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Asserts that the value is a node of designated subtype
        ' @param {Dynamic} value - value to check - target node
        ' @param {String} typeStr - type name
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertSubType(value as dynamic, typeStr as string, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(value) <> "roSGNode" then
                    actual = rooibos.common.getTypeWithComponentWrapper(value)
                    expected = `<Component: roSGNode:${typeStr}>`

                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                else if value.subType() <> typeStr then
                    actual = rooibos.common.getTypeWithComponentWrapper(value, true)
                    expected = `<Component: roSGNode:${typeStr}>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        function assertClass(value as dynamic, expectedClassName as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            if rooibos.common.isFunction(expectedClassName) then
                expectedClassName = expectedClassName.toStr().mid(10).replace("_", ".")
            end if

            try
                if not rooibos.common.isAssociativeArray(value) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}" to be an instance of ${rooibos.common.truncateString(rooibos.common.asMultilineString(expectedClassName, true))}`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isString(value?.__classname) then
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(value, true))}" to be an instance of ${rooibos.common.truncateString(rooibos.common.asMultilineString(expectedClassName, true))}`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                className = value?.__classname
                fail = false
                if not rooibos.common.isString(value?.__classname) then
                    className = "Invalid"
                    fail = true
                end if
                className = lCase(className)

                if fail or className <> lCase(expectedClassName) then
                    actual = rooibos.common.asMultilineString(className, true)
                    expected = rooibos.common.asMultilineString(lCase(expectedClassName), true)
                    if msg = "" then
                        msg = `expected class ${rooibos.common.truncateString(actual)} to be an instance of ${rooibos.common.truncateString(expected)}`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function


        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ NEW NODE ASSERTS
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        ' Asserts that the node contains the designated number of children
        ' @param {Dynamic} node - target node
        ' @param {Integer} count - expected number of child items
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert w, false otherwise
        function assertNodeCount(node as dynamic, count as integer, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(node) = "roSGNode" then
                    if node.isSubType("mc_Node") then
                        childCount = node.length
                    else
                        childCount = node.getChildCount()
                    end if
                    if childCount <> count then
                        actual = rooibos.common.asMultilineString(childCount, true)
                        expected = rooibos.common.asMultilineString(count, true)
                        if msg = "" then
                            msg = `expected count "${actual}" to be "${expected}"`
                        end if
                        m.fail(msg, actual, expected, true)
                        return false
                    end if
                else
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the node items count = expected count.
        ' @param {Dynamic} node - A target node
        ' @param {Integer} count - Expected item count
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNodeNotCount(node as dynamic, count as integer, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(node) = "roSGNode" then
                    if node.isSubType("mc_Node") then
                        childCount = node.length
                    else
                        childCount = node.getChildCount()
                    end if
                    if childCount = count then
                        actual = rooibos.common.asMultilineString(childCount, true)
                        expected = rooibos.common.asMultilineString(count, true)
                        if msg = "" then
                            msg = `expected count "${actual}" to not be "${expected}"`
                        end if
                        m.fail(msg, actual, expected, true)
                        return false
                    end if
                else
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Asserts the node has no children
        ' @param {Dynamic} node - a node to check
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNodeEmpty(node as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(node) = "roSGNode" then
                    if node.isSubType("mc_Node") then
                        childCount = node.length
                    else
                        childCount = node.getChildCount()
                    end if
                    if childCount > 0 then
                        if msg = "" then
                            msg = `expected child count "${childCount}" to be "0"`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                else
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Asserts the node has children
        ' @param {Dynamic} node - a node to check
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNodeNotEmpty(node as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(node) = "roSGNode" then
                    if node.isSubType("mc_Node") then
                        childCount = node.length
                    else
                        childCount = node.getChildCount()
                    end if

                    if childCount = 0 then
                        if msg = "" then
                            msg = `expected child count "${childCount}" to be greater then "0"`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                else
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Asserts the node contains the child _value_
        ' @param {Dynamic} node - a node to check
        ' @param {Dynamic} value - value to check - value to look for
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNodeContains(node as dynamic, value as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(node) = "roSGNode" then
                    if not rooibos.common.nodeContains(node, value) then
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                else
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Asserts the node contains only the child _value_
        ' @param {Dynamic} node - a node to check
        ' @param {Dynamic} value - value to check - value to look for
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNodeContainsOnly(node as dynamic, value as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if type(node) = "roSGNode" then
                    if not rooibos.common.nodeContains(node, value) then
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    else
                        if node.isSubType("mc_Node") then
                            childCount = node.length
                        else
                            childCount = node.getChildCount()
                        end if

                        if childCount <> 1 then
                            if msg = "" then
                                msg = `expected child count "${childCount}" to be "1"`
                            end if
                            m.fail(msg, "", "", true)
                            return false
                        end if
                    end if
                else
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function


        ' Fail if the node h item.
        ' @param {Dynamic} node - A target node
        ' @param {Dynamic} value - value to check - a node child
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNodeNotContains(node as dynamic, value as dynamic, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if
            try
                if type(node) = "roSGNode" then
                    if rooibos.common.nodeContains(node, value) then
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to not contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference`
                        end if
                        m.fail(msg, "", "", true)
                        return false
                    end if
                else
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        ' Fail if the node doesn't have the item subset.
        ' @param {Dynamic} node - A target node
        ' @param {Dynamic} subset - items to check
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertNodeContainsFields(node as dynamic, subset as dynamic, ignoredFields = invalid as string[], msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not type(node) = "roSGNode" then
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if

                if not rooibos.common.isAssociativeArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isArray(ignoredFields) then
                    filteredSubset = {}
                    for each key in subset
                        if rooibos.common.isString(key) and not rooibos.common.arrayContains(ignoredFields, key) then
                            filteredSubset[key] = subset[key]
                        end if
                    end for
                else
                    filteredSubset = subset
                end if

                foundValues = {}
                missingValues = {}
                for each key in filteredSubset
                    subsetValue = filteredSubset[key]
                    nodeValue = node[key]
                    if rooibos.common.eqValues(nodeValue, subsetValue) then
                        foundValues[key] = subsetValue
                    else
                        missingValues[key] = subsetValue
                    end if
                end for

                if foundValues.ifAssociativeArray.count() <> filteredSubset.ifAssociativeArray.count() then
                    actual = rooibos.common.asMultilineString(foundValues, true)
                    expected = rooibos.common.asMultilineString(filteredSubset, true)
                    if msg = "" then
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to have properties "${rooibos.common.truncateString(rooibos.common.asMultilineString(missingValues))}"`
                        end if
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, error.message)
            end try
            return false
        end function

        ' Fail if the node have the item from subset.
        ' @param {Dynamic} node - A target node
        ' @param {Dynamic} subset - the items to check for
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert w, false otherwise
        function assertNodeNotContainsFields(node as dynamic, subset as roArray or roAssociativeArray, msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not type(node) = "roSGNode" then
                    actual = rooibos.common.getTypeWithComponentWrapper(node)
                    expected = `<Component: roSGNode>`
                    if msg = "" then
                        msg = `expected type "${rooibos.common.truncateString(actual)}" to be type "${rooibos.common.truncateString(expected)}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if

                if rooibos.common.isArray(subset) then
                    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                    ' NOTE: Legacy check for children via array support.
                    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                    for each value in subset
                        if rooibos.common.nodeContains(node, value) then
                            if msg = "" then
                                msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to not contain child "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(value, true))}" by reference`
                            end if
                            m.fail(msg, "", "", true)
                            return false
                        end if
                    end for
                else if rooibos.common.isAssociativeArray(subset) then
                    foundValues = {}
                    for each key in subset
                        subsetValue = subset[key]
                        nodeValue = node[key]
                        if rooibos.common.eqValues(nodeValue, subsetValue) then
                            foundValues[key] = subsetValue
                        end if
                    end for

                    if foundValues.ifAssociativeArray.count() > 0 then
                        actual = rooibos.common.asMultilineString(foundValues, true)
                        expected = rooibos.common.asMultilineString({}, true)
                        if msg = "" then
                            msg = `expected "${rooibos.common.truncateString(rooibos.common.getTypeWithComponentWrapper(node, true))}" to have not have properties "${rooibos.common.truncateString(rooibos.common.asMultilineString(foundValues))}"`
                        end if
                        m.fail(msg, actual, expected, true)
                        return false
                    end if
                else
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function

        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ END NODE ASSERTS
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        ' Asserts the associative array contains the fields contained in subset; while ignoring the fields in the ignoredFields array
        ' @param {roAssociativeArray} array - associative array  to check
        ' @param {roAssociativeArray} subset - associative array of values to check for
        ' @param {roArray} ignoredFields - array of field names to ignore while comparing
        ' @param {String} [msg=""] - alternate error message
        ' @returns {Boolean} true if the assert was satisfied, false otherwise
        function assertAAContainsSubset(array as dynamic, subset as roAssociativeArray, ignoredFields = invalid as string[], msg = "" as string) as boolean
            if m.currentResult.isFail then
                return false
            end if

            try
                if not rooibos.common.isAssociativeArray(array) then
                    if msg = "" then
                        msg = `expected target "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if not rooibos.common.isAssociativeArray(subset) then
                    if msg = "" then
                        msg = `expected subset "${rooibos.common.truncateString(rooibos.common.asMultilineString(subset, true))}" to be an AssociativeArray`
                    end if
                    m.fail(msg, "", "", true)
                    return false
                end if

                if rooibos.common.isArray(ignoredFields) then
                    filteredSubset = {}
                    for each key in subset
                        if rooibos.common.isString(key) and not rooibos.common.arrayContains(ignoredFields, key) then
                            filteredSubset[key] = subset[key]
                        end if
                    end for
                else
                    filteredSubset = subset
                end if

                foundValues = {}
                missingValues = {}
                for each key in filteredSubset
                    subsetValue = filteredSubset[key]
                    nodeValue = array[key]
                    if rooibos.common.eqValues(nodeValue, subsetValue) then
                        foundValues[key] = subsetValue
                    else
                        missingValues[key] = subsetValue
                    end if
                end for

                if foundValues.ifAssociativeArray.count() <> filteredSubset.ifAssociativeArray.count() then
                    actual = rooibos.common.asMultilineString(foundValues, true)
                    expected = rooibos.common.asMultilineString(filteredSubset, true)
                    if msg = "" then
                        msg = `expected "${rooibos.common.truncateString(rooibos.common.asMultilineString(array, true))}" to have properties "${rooibos.common.truncateString(rooibos.common.asMultilineString(missingValues))}"`
                    end if
                    m.fail(msg, actual, expected, true)
                    return false
                end if
                return true
            catch error
                m.currentResult.failCrash(error, msg)
            end try
            return false
        end function


        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ Stubbing helpers
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        ' Creates a stub to replace a real method with
        ' @param {Dynamic} target - object on which the method to be stubbed is found
        ' @param {Dynamic} methodName - name of method to stub
        ' @param {Dynamic} [returnValue=invalid] - value that the stub method will return when invoked
        ' @param {Boolean} [allowNonExistingMethods=false] - if true, then rooibos will only warn if the method did not exist prior to faking
        ' @returns {Object} stub that was wired into the real method
        function stub(target as roAssociativeArray, methodName as dynamic, returnValue = invalid as dynamic, allowNonExistingMethods = false as boolean) as roAssociativeArray
            if target = invalid and not rooibos.common.isFunction(methodName) then
                m.fail("could not create Stub. Global function", methodName, ", is invalid")
                return {}
            else if type(target) <> "roAssociativeArray" then
                m.fail("could not create Stub provided target was null")
                return {}
            end if

            if m.stubs = invalid then
                m.__stubId = -1
                m.stubs = {}
            end if
            m.__stubId++

            if m.__stubId > 25 then
                rooibos.common.logError(`ERROR ONLY 25 MOCKS PER TEST ARE SUPPORTED!! you're on # ${m.__mockId}`)
                rooibos.common.logError("Method was " + methodName)
                return invalid
            end if

            id = strI(m.__stubId).trim()

            fake = m.createFake(id, target, methodName, 1, invalid, returnValue)
            m.stubs[id] = fake
            if target.isGlobalCall = true then
                rooibos.getMocksByFunctionName()[methodName] = fake
            else
                allowNonExisting = m.allowNonExistingMethodsOnMocks = true or allowNonExistingMethods
                isMethodPresent = type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction"
                if isMethodPresent or allowNonExisting then
                    target[methodName] = m["StubCallback" + id]
                    target.__stubs = m.stubs

                    ' FIXME: add a log setting for this - and add better detection so that stubs know that they are colliding/don't exist/have correct sigs
                    ' if not isMethodPresent
                    '   rooibos.common.logWarning(`stubbing call ${methodName} which did not exist on target object`)
                    ' end if
                else
                    rooibos.common.logTrace("Could not create Stub : method not found  " + rooibos.common.asString(target) + "." + methodName)
                end if
            end if

            return fake
        end function

        function expectLastCallToThrowError(error as dynamic)
            try
                mock = m.mocks[m.__mockId.toStr()]
                mock.toThrow(error)
            catch error
                m.log.error("could not add throw to last call", error)
            end try
        end function

        function expectCalled(invocation as dynamic, returnValue = invalid as dynamic) as object
            'mock function body - the plugin replaces this
            return invalid
        end function

        ' @ignore
        function _expectCalled(target as dynamic, methodName as dynamic, rootObject = invalid as dynamic, fullPath = invalid as dynamic, expectedArgs = invalid as dynamic, returnValue = invalid as dynamic) as roAssociativeArray
            try
                if type(target) <> "roAssociativeArray" and fullPath <> invalid then
                    target = rooibos.common.makePathStubbable(rootObject, fullPath)
                end if
                return m.mock(target, methodName, 1, expectedArgs, returnValue, true)
            catch error
                m.fail("Setting up mock failed: " + error.message, "", "", true)
            end try
            return invalid
        end function

        function stubCall(invocation as dynamic, stubOrReturnValue = invalid as dynamic, functionName = "" as string) as roAssociativeArray
            ' When stubbing global functions this will be called. Other wise the test code will be updated to call m._stubCall()
            if type(invocation).endsWith("Function") and functionName = "" then
                functionName = invocation.toStr().tokenize(" ").peek()
            else
                throw "Did not provide a function to be stubbed"
            end if

            if not type(stubOrReturnValue).endsWith("Function") then
                ' throw "Did not provide a stub function"
            end if

            ' Store the stub on the component scope
            globalAa = getGlobalAa()
            if type(globalAa?.__globalStubs) <> "roAssociativeArray" then
                globalAa.__globalStubs = {}
            end if

            globalAa.__globalStubs[lCase(functionName)] = stubOrReturnValue
            return invalid
        end function

        ' @ignore
        function _stubCall(target as dynamic, methodName as dynamic, rootObject = invalid as dynamic, fullPath = invalid as dynamic, returnValue = invalid as dynamic) as object
            try
                if type(target) <> "roAssociativeArray" and fullPath <> invalid then
                    target = rooibos.common.makePathStubbable(rootObject, fullPath)
                end if
                return m.stub(target, methodName, returnValue, true)
            catch error
                m.fail("Setting up mock failed: " + error.message, "", "", true)
            end try
            return false
        end function

        function expectNotCalled(invocation as dynamic) as object
            'mock function body - the plugin replaces this
            return invalid
        end function

        ' @ignore
        function _expectNotCalled(target as dynamic, methodName as dynamic, rootObject = invalid as dynamic, fullPath = invalid as dynamic) as object
            try
                if type(target) <> "roAssociativeArray" and fullPath <> invalid then
                    target = rooibos.common.makePathStubbable(rootObject, fullPath)
                end if
                return m.mock(target, methodName, 0, invalid, invalid, true)
            catch error
                m.fail("Setting up mock failed: " + error.message, "", "", true)
            end try
            return false
        end function


        ' Creates a stub to replace a real method with, which the framework will track. If it was invoked the wrong number of times, or with wrong arguments, it will result in test failure
        ' @param {Dynamic} target - object on which the method to be stubbed is found
        ' @param {Dynamic} methodName - name of method to stub
        ' @param {Dynamic} [expectedArgs=invalid] - array containing the arguments we expect the method to be invoked with
        ' @param {Dynamic} [returnValue=invalid] - value that the stub method will return when invoked
        ' @param {Boolean} [allowNonExistingMethods=false] - if true, then rooibos will only warn if the method did not exist prior to faking
        ' @returns {Object} mock that was wired into the real method
        function expectOnce(target as dynamic, methodName as dynamic, expectedArgs = invalid as dynamic, returnValue = invalid as dynamic, allowNonExistingMethods = false as boolean) as object
            'HACK
            'HACK
            'HACK
            'HACK
            'HACK
            ' try
            return m.mock(target, methodName, 1, expectedArgs, returnValue, allowNonExistingMethods)
            ' catch error
            '   m.fail("Setting up mock failed: " + error.message, "", "", true)
            '   return false
            ' end try
            ' return false
        end function

        ' Toggles between expectOnce and expectNone, to allow for easy parameterized expect behavior
        ' @param {Dynamic} target - object on which the method to be stubbed is found
        ' @param {Dynamic} methodName - name of method to stub
        ' @param {Dynamic} isExpected - if true, then this is the same as expectOnce, if false, then this is the same as expectNone
        ' @param {Dynamic} [expectedArgs=invalid] - array containing the arguments we expect the method to be invoked with
        ' @param {Dynamic} [returnValue=invalid] - value that the stub method will return when invoked
        ' @param {Boolean} [allowNonExistingMethods=false] - if true, then rooibos will only warn if the method did not exist prior to faking
        ' @returns {Object} mock that was wired into the real method
        function expectOnceOrNone(target as dynamic, methodName as dynamic, isExpected as dynamic, expectedArgs = invalid as dynamic, returnValue = invalid as dynamic, allowNonExistingMethods = false as boolean) as dynamic
            try
                if isExpected then
                    return m.expectOnce(target, methodName, expectedArgs, returnValue, allowNonExistingMethods)
                else
                    return m.expectNone(target, methodName, allowNonExistingMethods)
                end if
            catch error
                m.fail("Setting up mock failed: " + error.message, "", "", true)
            end try
            return false
        end function

        ' Creates a stub to replace a real method with, which the framework will track. If it was invoked, it will result in test failure
        ' @param {Dynamic} target - object on which the method to be stubbed is found
        ' @param {Dynamic} methodName - name of method to stub
        ' @param {Boolean} [allowNonExistingMethods=false] - if true, then rooibos will only warn if the method did not exist prior to faking
        ' @returns {Object} mock that was wired into the real method
        function expectNone(target as dynamic, methodName as dynamic, allowNonExistingMethods = false as boolean) as object
            try
                return m.mock(target, methodName, 0, invalid, invalid, allowNonExistingMethods)
            catch error
                m.fail("Setting up mock failed: " + error.message, "", "", true)
            end try
            return false
        end function

        ' Creates a stub to replace a real method with, which the framework will track. If it was invoked the wrong number of times, or with wrong arguments, it will result in test failure
        ' @param {Dynamic} target - object on which the method to be stubbed is found
        ' @param {Dynamic} methodName - name of method to stub
        ' @param {Dynamic} [expectedInvocations=1] - number of invocations we expect
        ' @param {Dynamic} [expectedArgs=invalid] - array containing the arguments we expect the method to be invoked with
        ' @param {Dynamic} [returnValue=invalid] - value that the stub method will return when invoked
        ' @param {Boolean} [allowNonExistingMethods=false] - if true, then rooibos will only warn if the method did not exist prior to faking
        ' @returns {Object} mock that was wired into the real method
        function expect(target as dynamic, methodName as dynamic, expectedInvocations = 1 as integer, expectedArgs = invalid as dynamic, returnValue = invalid as dynamic, allowNonExistingMethods = false as boolean) as object
            try
                return m.mock(target, methodName, expectedInvocations, expectedArgs, returnValue, allowNonExistingMethods)
            catch error
                m.fail("Setting up mock failed: " + error.message, "", "", true)
            end try
            return false
        end function

        ' Creates a stub to replace a real method with, which the framework will track. If it was invoked the wrong number of times, or with wrong arguments, it will result in test failure
        ' @param {Dynamic} target - object on which the method to be stubbed is found
        ' @param {Dynamic} methodName - name of method to stub
        ' @param {Dynamic} expectedInvocations - number of invocations we expect
        ' @param {Dynamic} [expectedArgs=invalid] - array containing the arguments we expect the method to be invoked with
        ' @param {Dynamic} [returnValue=invalid] - value that the stub method will return when invoked
        ' @param {Boolean} [allowNonExistingMethods=false] - if true, then rooibos will only warn if the method did not exist prior to faking
        ' @returns {Object} mock that was wired into the real method
        function mock(target as dynamic, methodName as dynamic, expectedInvocations = 1 as integer, expectedArgs = invalid as dynamic, returnValue = invalid as dynamic, allowNonExistingMethods = false as boolean) as object
            lineNumber = m.currentAssertLineNumber
            'check params

            if target <> invalid and not rooibos.common.isFunction(target) and not rooibos.common.isAssociativeArray(target) then
                methodName = ""
                m.mockFail(lineNumber, "", "mock args: target should be an AA or in-scope Global function", methodName)
            else if not rooibos.common.isString(methodName) then
                methodName = ""
                m.mockFail(lineNumber, "", "mock args: methodName was not a string")
            else if not rooibos.common.isNumber(expectedInvocations) then
                m.mockFail(lineNumber, methodName, "mock args: expectedInvocations was not an int")
            else if not rooibos.common.isArray(expectedArgs) and rooibos.common.isValid(expectedArgs) then
                m.mockFail(lineNumber, methodName, "mock args: expectedArgs was not invalid or an array of args")
            else if rooibos.common.isUndefined(expectedArgs) then
                m.mockFail(lineNumber, methodName, "mock args: expectedArgs undefined")
            else if rooibos.common.isUndefined(returnValue) then
                m.mockFail(lineNumber, methodName, "mock args: returnValue undefined")
            end if

            if m.currentResult.isFail then
                rooibos.common.logError(`Cannot create MOCK. method ${methodName} ${lineNumber} ${m.currentResult.message}`)
                return {}
            end if

            if m.mocks = invalid then
                m.__mockId = -1
                m.__mockTargetId = -1
                m.mocks = {}
                rooibos.resetMocksByFunctionName()
            end if

            fake = invalid
            if rooibos.common.isFunction(target) then
                target = {
                    isGlobalCall: true
                }
            end if
            if not target.doesExist("__rooibosTargetId") then
                m.__mockTargetId++
                target["__rooibosTargetId"] = m.__mockTargetId
            end if
            'ascertain if mock already exists
            for i = 0 to m.__mockId
                id = strI(i).trim()
                mock = m.mocks[id]
                if mock <> invalid and mock.methodName = methodName and (mock.target.__rooibosTargetId = target.__rooibosTargetId or (mock.target.isGlobalCall = true and target.isGlobalCall = true)) then
                    fake = mock
                    fake.lineNumbers.push(lineNumber)
                    exit for
                end if
            end for
            if fake = invalid then
                m.__mockId++
                id = strI(m.__mockId).trim()
                if m.__mockId > 25 then
                    rooibos.common.logError(`ERROR ONLY 25 MOCKS PER TEST ARE SUPPORTED!! you're on # ${m.__mockId}`)
                    rooibos.common.logError("Method was " + methodName)
                    return invalid
                end if

                fake = m.createFake(id, target, methodName, expectedInvocations, expectedArgs, returnValue)
                m.mocks[id] = fake 'this will bind it to m
                if target.isGlobalCall = true then
                    rooibos.getMocksByFunctionName()[methodName] = fake
                else
                    allowNonExisting = m.allowNonExistingMethodsOnMocks = true or allowNonExistingMethods
                    isMethodPresent = type(target[methodName]) = "Function" or type(target[methodName]) = "roFunction"
                    if isMethodPresent or allowNonExisting then
                        target[methodName] = m["MockCallback" + id]
                        target.__mocks = m.mocks

                        if not isMethodPresent then
                            rooibos.common.logWarning(`mocking call ${methodName} which did not exist on target object`)
                        end if
                    else
                        rooibos.common.logError(`Could not create Mock : method not found ${target}.${methodName}`)
                    end if
                end if
            else
                m.combineFakes(fake, m.createFake(id, target, methodName, expectedInvocations, expectedArgs, returnValue))
            end if

            return fake
        end function

        ' Creates a stub to replace a real method with. This is used internally.
        ' @param {Dynamic} target - object on which the method to be stubbed is found
        ' @param {Dynamic} methodName - name of method to stub
        ' @param {Dynamic} [expectedInvocations=1] - number of invocations we expect
        ' @param {Dynamic} [expectedArgs=invalid] - array containing the arguments we expect the method to be invoked with
        ' @param {Dynamic} [returnValue=invalid] - value that the stub method will return when invoked
        ' @returns {Object} stub that was wired into the real method
        function createFake(id as dynamic, target as dynamic, methodName as dynamic, expectedInvocations = 1 as integer, expectedArgs = invalid as dynamic, returnValue = invalid as dynamic) as object
            expectedArgsValues = []
            lineNumber = m.currentAssertLineNumber
            hasArgs = rooibos.common.isArray(expectedArgs)
            defaultValue = m.ignoreValue
            if hasArgs then
                defaultValue = m.invalidValue
            else
                expectedArgs = []
            end if

            lineNumbers = [lineNumber]

            for i = 0 to 9
                if hasArgs and expectedArgs.count() > i then
                    'guard against bad values
                    value = expectedArgs[i]
                    if not rooibos.common.isUndefined(value) then
                        if rooibos.common.isAssociativeArray(value) and rooibos.common.isValid(value.matcher) then
                            if not rooibos.common.isFunction(value.matcher) then
                                rooibos.common.logError("You have specified a matching function; but it is not in scope!")
                                expectedArgsValues.push("#ERR-OUT_OF_SCOPE_MATCHER!")
                            else
                                expectedArgsValues.push(expectedArgs[i])
                            end if
                        else
                            expectedArgsValues.push(expectedArgs[i])
                        end if
                    else
                        expectedArgsValues.push("#ERR-UNDEFINED!")
                    end if
                else
                    expectedArgsValues.push(defaultValue)
                end if
            end for
            'todo - make into a class
            fake = {
                id: id
                target: target
                errorToThrow: invalid
                methodName: methodName
                returnValue: returnValue
                lineNumbers: lineNumbers
                isCalled: false
                invocations: 0
                invokedArgs: [invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid, invalid]
                expectedArgs: expectedArgsValues
                expectedInvocations: expectedInvocations
                callback: function(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
                    rooibos.common.logTrace(`FAKE CALLBACK CALLED FOR ${m.methodName}`)
                    if m.allInvokedArgs = invalid then
                        m.allInvokedArgs = []
                    end if
                    m.invokedArgs = [arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15]
                    m.allInvokedArgs.push ([arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15])
                    m.isCalled = true
                    m.invocations++

                    if m.errorToThrow <> invalid then
                        throw m.errorToThrow
                    end if
                    if type(m.returnValue) = "roAssociativeArray" and m.returnValue.ifAssociativeArray.doesExist("multiResult") then
                        returnValues = m.returnValue["multiResult"]
                        returnIndex = m.invocations - 1

                        if type(returnValues) = "roArray" and returnValues.count() > 0 then
                            if returnValues.count() <= m.invocations then
                                returnIndex = returnValues.count() - 1
                                rooibos.common.logDebug("Multi return values all used up - repeating last value")
                            end if
                            return returnValues[returnIndex]
                        else
                            rooibos.common.logError("Multi return value was specified; but no array of results were found")
                            return invalid
                        end if
                    else
                        return m.returnValue
                    end if
                end function
                toThrow: function(error as roAssociativeArray)
                    m.errorToThrow = error
                end function
            }
            return fake
        end function

        function combineFakes(fake as roAssociativeArray, otherFake as roAssociativeArray)
            'add on the expected invoked args
            lineNumber = m.currentAssertLineNumber
            if type(fake.expectedArgs) <> "roAssociativeArray" or not fake.expectedArgs.ifAssociativeArray.doesExist("multiInvoke") then
                currentExpectedArgsArgs = fake.expectedArgs
                fake.expectedArgs = {
                    "multiInvoke": [currentExpectedArgsArgs]
                }
            end if
            for i = 1 to otherFake.expectedInvocations
                fake.expectedArgs.multiInvoke.push(otherFake.expectedArgs)
            end for

            'add on the expected return values
            if type(fake.returnValue) <> "roAssociativeArray" or not fake.returnValue.ifAssociativeArray.doesExist("multiResult") then
                currentReturnValue = fake.returnValue
                fake.returnValue = {
                    "multiResult": [currentReturnValue]
                }
            end if
            for i = 1 to otherFake.expectedInvocations
                fake.returnValue.multiResult.push(otherFake.returnValue)
            end for
            fake.lineNumbers.push(lineNumber)
            fake.expectedInvocations += otherFake.expectedInvocations
        end function

        ' Will check all mocks that have been created to ensure they were invoked the expected amount of times, with the expected args.
        function assertMocks() as void
            if m.__mockId = invalid or not rooibos.common.isAssociativeArray(m.mocks) then
                return
            end if

            for each id in m.mocks
                mock = m.mocks[id]
                methodName = mock.methodName
                if mock.expectedInvocations <> mock.invocations then
                    m.mockFail(mock.lineNumbers[0], methodName, "Wrong number of calls. (" + strI(mock.invocations).trim() + " / " + strI(mock.expectedInvocations).trim() + ")")
                    m.cleanMocks()
                    return
                else if mock.expectedInvocations > 0 and (rooibos.common.isArray(mock.expectedArgs) or (type(mock.expectedArgs) = "roAssociativeArray" and rooibos.common.isArray(mock.expectedArgs.multiInvoke))) then
                    isMultiArgsSupported = type(mock.expectedArgs) = "roAssociativeArray" and rooibos.common.isArray(mock.expectedArgs.multiInvoke)

                    for invocationIndex = 0 to mock.invocations - 1
                        invokedArgs = mock.allInvokedArgs[invocationIndex]
                        if isMultiArgsSupported then
                            expectedArgs = mock.expectedArgs.multiInvoke[invocationIndex]
                        else
                            expectedArgs = mock.expectedArgs
                        end if

                        if rooibos.common.isAssociativeArray(expectedArgs) then
                            expectedArgsCount = expectedArgs.ifAssociativeArray.count()
                        else
                            expectedArgsCount = expectedArgs.count()
                        end if

                        for i = 0 to expectedArgsCount - 1
                            value = invokedArgs[i]
                            expected = expectedArgs[i]
                            didNotExpectArg = rooibos.common.isString(expected) and expected = m.invalidValue
                            if didNotExpectArg then
                                expected = invalid
                            end if

                            isUsingMatcher = rooibos.common.isAssociativeArray(expected) and rooibos.common.isFunction(expected.matcher)

                            if isUsingMatcher then
                                if not expected.matcher(value) then
                                    m.mockFail(mock.lineNumbers[invocationIndex], methodName, "on Invocation #" + strI(invocationIndex).trim() + ", expected arg #" + strI(i).trim() + "  to match matching function '" + rooibos.common.asString(expected.matcher) + "' got '" + rooibos.common.asString(value, true) + "')")
                                    m.cleanMocks()
                                end if
                            else
                                if not (rooibos.common.isString(expected) and expected = m.ignoreValue) and not rooibos.common.eqValues(value, expected) then
                                    if expected = invalid then
                                        expected = "[INVALID]"
                                    end if

                                    m.mockFail(mock.lineNumbers[invocationIndex], methodName, "on Invocation #" + strI(invocationIndex).trim() + ", expected arg #" + strI(i).trim() + "  to be '" + rooibos.common.asString(expected, true) + "' got '" + rooibos.common.asString(value, true) + "')")
                                    m.cleanMocks()
                                    return
                                end if
                            end if
                        end for
                    end for
                end if
            end for

            m.cleanMocks()
        end function

        ' Cleans up all tracking data associated with mocks
        function cleanMocks() as void
            if m.mocks = invalid then
                return
            end if
            for each id in m.mocks
                mock = m.mocks[id]
                mock.target.__mocks = invalid
            end for
            m.mocks = invalid
            rooibos.resetMocksByFunctionName()
        end function


        ' Cleans up all tracking data associated with stubs
        function cleanStubs() as void
            ' Clean up the global functions mocks as well
            globalAa = getGlobalAa()
            globalAa.__globalStubs = invalid

            if m.stubs = invalid then
                return
            end if
            for each id in m.stubs
                stub = m.stubs[id]
                stub.target.__stubs = invalid
            end for
            m.stubs = invalid
            rooibos.resetMocksByFunctionName()
        end function


        function mockFail(lineNumber as integer, methodName as dynamic, message as string) as dynamic
            if m.currentResult.isFail then
                return false
            end if
            m.fail("mock failure on '" + methodName + "' : " + message, "", "", true)
            return false
        end function


        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ Fake Stub callback functions - this is required to get scope
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        ' @ignore
        private function stubCallback0(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["0"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback1(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["1"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback2(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["2"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback3(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["3"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback4(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["4"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback5(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["5"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback6(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["6"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback7(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["7"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback8(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["8"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback9(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["9"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback10(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["10"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback11(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["11"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback12(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["12"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback13(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["13"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback14(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["14"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback15(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["15"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback16(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["16"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback17(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["17"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback18(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["18"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback19(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["19"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback20(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["20"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback21(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["21"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback22(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["22"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback23(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["23"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback24(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["24"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function stubCallback25(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__Stubs["25"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function


        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ Fake Mock callback functions - this is required to get scope
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        ' @ignore
        private function mockCallback0(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["0"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback1(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["1"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback2(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["2"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        private function mockCallback3(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["3"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback4(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["4"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback5(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["5"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback6(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["6"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback7(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["7"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback8(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["8"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback9(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["9"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback10(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["10"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback11(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["11"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback12(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["12"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback13(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["13"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback14(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["14"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback15(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["15"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback16(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["16"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback17(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["17"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback18(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["18"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback19(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["19"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback20(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["20"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback21(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["21"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback22(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["22"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback23(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["23"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        ' @ignore
        private function mockCallback24(arg1 = invalid as dynamic, arg2 = invalid as dynamic, arg3 = invalid as dynamic, arg4 = invalid as dynamic, arg5 = invalid as dynamic, arg6 = invalid as dynamic, arg7 = invalid as dynamic, arg8 = invalid as dynamic, arg9 = invalid as dynamic, arg10 = invalid as dynamic, arg11 = invalid as dynamic, arg12 = invalid as dynamic, arg13 = invalid as dynamic, arg14 = invalid as dynamic, arg15 = invalid as dynamic) as dynamic
            fake = m.__mocks["24"]
            return fake.callback(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)
        end function

        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
        '++ crude async support
        '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

        ' observeField doesn't work in regular unit tests, so we have to wait for the result. We can use this to wait for a network task, foe example, and pass the result directly to a handler. Note - we wait for the value TO CHANGE - so make sure that will be the case, or you'll get stuck forever :)
        ' @param {any} target to observe
        ' @param {String} field to observe
        ' @param {int} delay for each wait
        ' @param {int} max attempts
        function waitForField(target as dynamic, fieldName as string, delay = 500 as integer, maxAttempts = 10 as integer) as boolean
            attempts = 0
            if target = invalid then
                return false
            end if

            initialValue = target[fieldName]
            while target[fieldName] = initialValue
                port = CreateObject("roMessagePort")
                wait(delay, port)
                attempts++
                if attempts = maxAttempts then
                    return false
                end if
                rooibos.common.logDebug(`Waiting for signal field '${fieldName}' - ${attempts}`)
            end while

            return true
        end function

        function wait(delay = 1 as integer)
            port = CreateObject("roMessagePort")
            wait(delay, port)
        end function

        function done()
            rooibos.common.logTrace("Async test is complete")
            if m.isDoneCalled = false then
                m.isDoneCalled = true
                deferred = m.currentGroup?.currentTest?.deferred
                if rooibos.promises.isPromise(deferred) then
                    rooibos.promises.resolve(true, deferred)
                end if
            else
                rooibos.common.logWarning("extra done call after test is done! Did you properly clean up your observers?")
            end if
        end function

        ' @ignore
        function testSuiteDone()
            rooibos.common.logTrace("Indicating test suite is done")
            m.notifyReportersOnSuiteComplete()
            m.testRunner.top.rooibosTestResult = {
                stats: m.stats
                tests: m.tests
                groups: m.groups
            }
        end function

        function assertAsyncField(target as dynamic, fieldName as string, delay = 500 as integer, maxAttempts = 10 as integer) as boolean
            try
                if m.currentResult.isFail then
                    return false
                end if
                if target = invalid then
                    m.fail("Target was invalid", "", "", true)
                end if

                result = m.waitForField(target, fieldName, delay, maxAttempts)
                if not result then
                    return m.fail("Timeout waiting for targetField " + fieldName + " to be set on target", "", "", true)
                end if

                return true
            catch error
                m.currentResult.fail("Error while waiting: " + error.message, m.currentAssertLineNumber)
            end try
            return false
        end function

        ' @ignore
        protected function createNodeClass(clazz as dynamic, useClassAsTop = true as boolean, nodeTop = new rooibos.utils.MockNode("top") as dynamic, nodeGlobal = new rooibos.utils.MockNode("top") as dynamic) as roAssociativeArray
            instance = tests_maestro_nodeClassUtils_createNodeClass(clazz, nodeTop, nodeGlobal) 'bs:disable-line 1140 LINT1001
            if instance <> invalid and useClassAsTop then
                'note - we use the clazz itself as TOP, so that we don't have to write tests that do
                'thing.top.value, thing.top.value2, etc all over the place
                instance.append(nodeTop)
                instance.top = instance
                instance.__rooibosSkipFields = { "top": true }
            end if
            return instance
        end function

        ' @ignore
        protected function createMockViews(instance as dynamic, bundlePath as string, viewsPath = "views" as string)
            bundle = m.global.testStyleManager@.loadBundle(bundlePath)
            ids = mv_getIdsFromStyleJson(mc_getArray(bundle, viewsPath)) 'bs:disable-line 1140 LINT1001
            for each id in ids
                instance[id] = { id: id }
            end for
        end function

        ' @ignore
        private function notifyReportersOnSuiteBegin()
            for each reporter in m.testReporters
                if rooibos.common.isFunction(reporter.onSuiteBegin) then
                    reporter.onSuiteBegin({ suite: m })
                end if
            end for
        end function

        ' @ignore
        private function notifyReportersOnSuiteComplete()
            for each reporter in m.testReporters
                if rooibos.common.isFunction(reporter.onSuiteComplete) then
                    reporter.onSuiteComplete({ suite: m })
                end if
            end for
        end function
    end class

    ' @ignore
    function getMocksByFunctionName() as dynamic
        if m._rMocksByFunctionName = invalid then
            m._rMocksByFunctionName = {}
        end if
        return m._rMocksByFunctionName
    end function

    ' @ignore
    function resetMocksByFunctionName()
        m._rMocksByFunctionName = invalid
    end function

    ' @ignore
    function getMockForFunction(functionName as string) as dynamic
        return rooibos.getMocksByFunctionName()[functionName]
    end function

    ' @ignore
    function isFunctionMocked(functionName as string) as boolean
        return rooibos.getMockForFunction(functionName) <> invalid
    end function
end namespace