Repository: sjbarag/brs
Pull request #622 (2021-03-01 22:57:19)

feat(mocks): mockFunction improvements
# Change summary

This improves `_brs_.mockFunction` in a couple of ways:
1. Allow calling without passing a substitute/mock (i.e. implicit mock)
1. Implement `_brs_.mockReturnValue` (named like [jest](https://jestjs.io/docs/en/mock-function-api#mockfnmockreturnvaluevalue))


Patch:
diff --git a/README.md b/README.md
index 4a2315e..d43d2f1 100644
--- a/README.md
+++ b/README.md
@@ -17,25 +17,7 @@
       - [Testing](#testing)
       - [Cleaning](#cleaning)
       - [All Together](#all-together)
-  - [Gaps](#gaps)
-  - [Extensions](#extensions)
-    - [`_brs_.getStackTrace(numEntries = 10, excludePatterns = [])`](#_brs_getstacktracenumentries--10-excludepatterns--)
-    - [`_brs_.global`](#_brs_global)
-    - [`_brs_.mockComponent(componentName as string, impl as AssocArray)`](#_brs_mockcomponentcomponentname-as-string-impl-as-assocarray)
-    - [`_brs_.mockFunction(funcName as string, impl as Function)`](#_brs_mockfunctionfuncname-as-string-impl-as-function)
-      - [Mock Function API](#mock-function-api)
-        - [`mock.calls`](#mockcalls)
-        - [`mock.clearMock()`](#mockclearmock)
-        - [`mock.getMockName()`](#mockgetmockname)
-        - [`mock.results`](#mockresults)
-    - [`_brs_.process`](#_brs_process)
-    - [`_brs_.resetMockComponent(componentName as string)`](#_brs_resetmockcomponentcomponentname-as-string)
-    - [`_brs_.resetMockComponents()`](#_brs_resetmockcomponents)
-    - [`_brs_.resetMockFunction(funcName as string)`](#_brs_resetmockfunctionfuncname-as-string)
-    - [`_brs_.resetMockFunctions()`](#_brs_resetmockfunctions)
-    - [`_brs_.resetMocks()`](#_brs_resetmocks)
-    - [`_brs_.runInScope(filePath as string, mainArgs as AssocArray)`](#_brs_runinscopefilepath-as-string-mainargs-as-assocarray)
-    - [`_brs_.triggerKeyEvent(key as string, press as boolean)`](#_brs_triggerkeyeventkey-as-string-press-as-boolean)
+  - [Documentation](#documentation)
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
@@ -166,235 +148,6 @@ Thanks to the [npm-run-all](https://www.npmjs.com/package/npm-run-all) package,
 $ yarn run-s clean build test
 ```
 
-## Gaps
-The API provided to BrightScript developers is quite large, and implementing it in its entirety is a daunting task.  Parts in the standard library included with the Reference BrightScript Implementation (RBI) are [listed in NotImplemented.md](./docs/NotImplemented.md).
+## Documentation
 
-## Extensions
-
-For the most part, `brs` attempts to emulate BrightScript as closely as possible. However, in the spirit of unit testing, it also has a few extensions that will help with testing.
-
-### `_brs_.getStackTrace(numEntries = 10, excludePatterns = [])`
-
-Prints out the stack trace. You can configure how many lines to print, and also use the `excludePatterns` arg, which is an **array of strings**, to exclude files/folders from being included in the trace. *Note: this function internally uses the [Javascript regex engine](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) to match the strings, not [Brightscript PCRE](https://developer.roku.com/en-ca/docs/references/brightscript/components/roregex.md).*
-
-```brs
-print _brs_.getStackTrace()
-' [
-'     "test/baz.test.brs:1:10",
-'     "lib/utils.brs:25:4",
-'     "foo/baz.brs:50:29"
-' ]
-
-print _brs_.getStackTrace(1)
-' [
-'     "test/baz.test.brs:1:10",
-' ]
-
-print _brs_.getStackTrace(10, ["lib"])
-' [
-'     "test/baz.test.brs:1:10",
-'     "foo/baz.brs:50:29"
-' ]
-```
-
-### `_brs_.global`
-
-Allows you to access `m.global` from inside your unit tests. Usage:
-
-```brightscript
-print _brs_.global
-' {
-'   someGlobalField: "someGlobalValue"
-' }
-```
-
-### `_brs_.mockComponent(componentName as string, impl as AssocArray)`
-
-Allows you to mock a component. Usage:
-
-```brightscript
-_brs_.mockComponent("ComponentName", {
-    someField: "foobar",
-    someFunc: sub()
-      return 123
-    end sub
-})
-```
-
-### `_brs_.mockFunction(funcName as string, impl as Function)`
-
-Allows you to mock a function. It also returns an associative array mock function (also known as a "spy" or "stub") that keeps track of calls and return values. Usage:
-
-```brightscript
-mock = _brs_.mockFunction("FunctionName", sub()
-    print "foobar"
-end sub)
-```
-
-#### Mock Function API
-
-The Mock Function API is modeled after [Jest's `mockFn` API](https://jestjs.io/docs/en/mock-function-api). Methods:
-
-##### `mock.calls`
-
-An array containing the arguments of each call to this function. Each item in the array is an array of the arguments for that call.
-
-For example:
-```brightscript
-mock = _brs_.mockFunction("fooBar", function(arg1 as string, arg2 as integer)
-    print "fooBar"
-end function)
-
-fooBar("baz", 123)
-print mock.calls
-' [
-'     ["baz", 123]
-' ]
-
-fooBar("lorem", 456)
-print mock.calls
-' [
-'     ["baz", 123]
-'     ["lorem", 456]
-' ]
-```
-
-##### `mock.clearMock()`
-
-Clears the `calls` and `results` arrays. Does not affect the mock implementation.
-
-```brightscript
-mock = _brs_.mockFunction("fooBar", function()
-    return "hello, world"
-end function)
-
-fooBar()
-print mock.calls.count()   ' => 1
-print mock.results.count() ' => 1
-
-mock.clearMock()
-print mock.calls.count()   ' => 0
-print mock.results.count() ' => 0
-```
-
-##### `mock.getMockName()`
-
-Returns the name of the mocked function.
-
-```brightscript
-mock = _brs_.mockFunction("fooBar", function()
-    return "hello, world"
-end function)
-
-print mock.getMockName() ' => "fooBar"
-```
-
-##### `mock.results`
-
-An array containing the return value for each call to this function. For example:
-
-```brightscript
-mock = _brs_.mockFunction("fooBar", function(arg as boolean)
-    if arg then return "foo" : else return "bar" : end if
-end function)
-
-fooBar(true)
-print mock.results ' => [ "foo" ]
-
-fooBar(false)
-print mock.results ' => [ "foo", "bar" ]
-```
-
-### `_brs_.process`
-
-Allows you to access the command line arguments and locale. Locale changes will be reflected in related `RoDeviceInfo` functions and the standard library `Tr` function. Usage:
-
-```brightscript
-print _brs_.process
-' {
-'   argv: [ "some", "arg" ],
-'   getLocale: [Function getLocale]
-'   setLocale: [Function setLocale]
-' }
-
-_brs_.process.setLocale("fr_CA")
-print _brs_.process.getLocale() ' => "fr_CA"
-print createObject("roDeviceInfo").getCurrentLocale() ' => "fr_CA"
-```
-
-### `_brs_.resetMockComponent(componentName as string)`
-
-Resets a specific component mock. Works on both partially mocked and fully mocked components. Usage:
-
-```brightscript
-_brs_.resetMockComponent("MyComponent")
-```
-
-### `_brs_.resetMockComponents()`
-
-Resets all component mocks. Usage:
-
-```brightscript
-_brs_.resetMockComponents()
-```
-
-### `_brs_.resetMockFunction(funcName as string)`
-
-Resets a specific function mock. Usage:
-
-```brightscript
-_brs_.resetMockFunction("MyFunction")
-```
-
-**Note:** If you have a mocked component that has a function with the same name, this _will not_ reset that component member function. For example:
-
-```brightscript
-_brs_.mockComponent("Component", {
-    foo: sub()
-        print "mock component foo"
-    end sub
-})
-_brs_.mockFunction("foo", sub()
-    print "mock global foo"
-end sub)
-
-node = createObject("roSGNode", "Component")
-foo() ' => "mock global foo"
-node.foo() ' => "mock component foo"
-
-_brs_.resetMockFunction("foo")
-foo() ' => "original implementation"
-node.foo() ' => "mock component foo"
-```
-
-### `_brs_.resetMockFunctions()`
-
-Resets all function mocks. Usage:
-
-```brightscript
-_brs_.resetMockFunctions()
-```
-
-### `_brs_.resetMocks()`
-
-Resets all component and function mocks. Usage:
-
-```brightscript
-_brs_.resetMocks()
-```
-
-### `_brs_.runInScope(filePath as string, mainArgs as AssocArray)`
-
-Runs a file (or set of files) **in the current global + module scope** with the provided arguments, returning either the value returned by those files' `main` function or `invalid` if an error occurs.
-
-```brightscript
-_brs_.runInScope("/path/to/myFile.brs", { foo: "bar" })
-```
-
-### `_brs_.triggerKeyEvent(key as string, press as boolean)`
-
-This will call `onKeyEvent` handlers up the focus chain until the event is handled. If there is no focused component, nothing will happen (which is how RBI behaves). Usage:
-
-```brs
-_brs_.triggerKeyEvent("OK", true)
-```
+For the most part, `brs` attempts to emulate BrightScript as closely as possible. However, there are certain implementation gaps. Also, in the spirit of unit testing, there are a few extensions that will help with testing. All of our documentation for APIs, extensions, gaps, and more is hosted on our docs site, [hulu.github.io/roca](https://hulu.github.io/roca).
diff --git a/docs/NotImplemented.md b/docs/NotImplemented.md
deleted file mode 100644
index b5c6df8..0000000
--- a/docs/NotImplemented.md
+++ /dev/null
@@ -1,159 +0,0 @@
-# Statements
-* [try/catch/throw](https://developer.roku.com/docs/references/brightscript/language/error-handling.md)
-* [goto](https://developer.roku.com/docs/references/brightscript/language/program-statements.md#goto-label)
-* [end](https://developer.roku.com/docs/references/brightscript/language/program-statements.md#end)
-* [stop](https://developer.roku.com/docs/references/brightscript/language/program-statements.md#stop) and the associated interactive debugger
-
-# Global Functions
-* [Eval](https://developer.roku.com/docs/references/brightscript/language/runtime-functions.md#evalcode-as-string-as-dynamic) - deprecated in RBI and will not be implemented in `brs`
-* [GetLastRunCompileError](https://developer.roku.com/docs/references/brightscript/language/runtime-functions.md#getlastruncompileerror-as-object) - compile errors are reported immediately
-* [GetLastRunRuntimeError](https://developer.roku.com/docs/references/brightscript/language/runtime-functions.md#getlastrunruntimeerror-as-integer) - runtime errors are reported immediately
-* [RunGarbageCollector](https://developer.roku.com/docs/references/brightscript/language/global-utility-functions.md#rungarbagecollector-as-object)
-* [Sleep](https://developer.roku.com/docs/references/brightscript/language/global-utility-functions.md#sleepmilliseconds-as-integer-as-void)
-* [Wait](https://developer.roku.com/docs/references/brightscript/language/global-utility-functions.md#waittimeout-as-integer-port-as-object-as-object) - requires `roMessagePort` to be implemented (see below)
-* [UpTime](https://developer.roku.com/docs/references/brightscript/language/global-utility-functions.md#uptimedummy-as-integer-as-float)
-
-# BrightScript Components
-The following components are not currently implemented in `brs`, meaning that they can't be instantiated via `createObject`.  They may be implemented in the future
-
-* [roAppInfo](https://developer.roku.com/docs/references/brightscript/components/roappinfo.md)
-* [roAppManager](https://developer.roku.com/docs/references/brightscript/components/roappmanager.md)
-* [roAudioGuide](https://developer.roku.com/docs/references/brightscript/components/roaudioguide.md)
-* [roAudioMetadata](https://developer.roku.com/docs/references/brightscript/components/roaudioguide.md)
-* [roAudioPlayer](https://developer.roku.com/docs/references/brightscript/components/roaudioplayer.md)
-* [roAudioResource](https://developer.roku.com/docs/references/brightscript/components/roaudioresource.md)
-* [roBitmap](https://developer.roku.com/docs/references/brightscript/components/robitmap.md)
-* [roByteArray](https://developer.roku.com/docs/references/brightscript/components/robytearray.md)
-* [roChannelStore](https://developer.roku.com/docs/references/brightscript/components/rochannelstore.md)
-* [roCompositor](https://developer.roku.com/docs/references/brightscript/components/rocompositor.md)
-* [roDataGramSocket](https://developer.roku.com/docs/references/brightscript/components/rodatagramsocket.md)
-* [roDeviceCrypto](https://developer.roku.com/docs/references/brightscript/components/rodevicecrypto.md)
-* [roEVPCipher](https://developer.roku.com/docs/references/brightscript/components/roevpcipher.md)
-* [roEVPDigest](https://developer.roku.com/docs/references/brightscript/components/roevpdigest.md)
-* [roEVPDigest](https://developer.roku.com/docs/references/brightscript/components/roevpdigest.md)
-* [roFileSystem](https://developer.roku.com/docs/references/brightscript/components/rofilesystem.md)
-* [roFileSystem](https://developer.roku.com/docs/references/brightscript/components/rofilesystem.md)
-* [roFont](https://developer.roku.com/docs/references/brightscript/components/rofont.md)
-* [roFontRegistry](https://developer.roku.com/docs/references/brightscript/components/rofontregistry.md)
-* [roFunction](https://developer.roku.com/docs/references/brightscript/components/rofunction.md)
-* [roHdmiStatus](https://developer.roku.com/docs/references/brightscript/components/rohdmistatus.md)
-* [roHMAC](https://developer.roku.com/docs/references/brightscript/components/rohmac.md)
-* [roHttpAgent](https://developer.roku.com/docs/references/brightscript/components/rohttpagent.md)
-* [roHttpAgent](https://developer.roku.com/docs/references/brightscript/components/rohttpagent.md)
-* [roImageMetadata](https://developer.roku.com/docs/references/brightscript/components/roimagemetadata.md)
-* [roInput](https://developer.roku.com/docs/references/brightscript/components/roinput.md)
-* [roList](https://developer.roku.com/docs/references/brightscript/components/rolist.md)
-* [roLocalization](https://developer.roku.com/docs/references/brightscript/components/rolocalization.md)
-* [roLongInteger](https://developer.roku.com/docs/references/brightscript/components/rolonginteger.md)
-* [roMessagePort](https://developer.roku.com/docs/references/brightscript/components/romessageport.md)
-* [roMicrophone](https://developer.roku.com/docs/references/brightscript/components/romicrophone.md)
-* [roPath](https://developer.roku.com/docs/references/brightscript/components/ropath.md)
-* [roProgramGuide](https://developer.roku.com/docs/references/brightscript/components/roprogramguide.md)
-* [roRegion](https://developer.roku.com/docs/references/brightscript/components/roregion.md)
-* [roRegistry](https://developer.roku.com/docs/references/brightscript/components/roregistry.md)
-* [roRegistrySection](https://developer.roku.com/docs/references/brightscript/components/roregistrysection.md)
-* [roRSA](https://developer.roku.com/docs/references/brightscript/components/rorsa.md)
-* [roScreen](https://developer.roku.com/docs/references/brightscript/components/roscreen.md)
-* [roSGScreen](https://developer.roku.com/docs/references/brightscript/components/rosgscreen.md)
-* [roSocketAddress](https://developer.roku.com/docs/references/brightscript/components/rosocketaddress.md)
-* [roSpringboardScreen](https://developer.roku.com/docs/references/brightscript/components/rospringboardscreen.md)
-* [roSprite](https://developer.roku.com/docs/references/brightscript/components/rosprite.md)
-* [roStreamSocket](https://developer.roku.com/docs/references/brightscript/components/rostreamsocket.md)
-* [roSystemLog](https://developer.roku.com/docs/references/brightscript/components/rosystemlog.md)
-* [roTextToSpeech](https://developer.roku.com/docs/references/brightscript/components/rotexttospeech.md)
-* [roTextureManager](https://developer.roku.com/docs/references/brightscript/components/rotexturemanager.md)
-* [roTextureRequest](https://developer.roku.com/docs/references/brightscript/components/rotexturerequest.md)
-* [roUrlTransfer](https://developer.roku.com/docs/references/brightscript/components/rourltransfer.md)
-* [roVideoPlayer](https://developer.roku.com/docs/references/brightscript/components/rovideoplayer.md)
-* [roXMLElement](https://developer.roku.com/docs/references/brightscript/components/roxmlelement.md)
-* [roXMLList](https://developer.roku.com/docs/references/brightscript/components/roxmllist.md)
-
-## Deprecated in RBI
-The following components are deprecated in the Reference BrightScript Interpreter (RBI), and thus will not be implemented in this project.
-
-* [roCaptionRenderer](https://developer.roku.com/docs/references/brightscript/components/robytearray.md)
-* [roCodeRegistrationScreen](https://developer.roku.com/docs/references/brightscript/components/rocoderegistrationscreen.md)
-* [roFontMetrics](https://developer.roku.com/docs/references/brightscript/components/rofontmetrics.md)
-* [roGridScreen](https://developer.roku.com/docs/references/brightscript/components/rogridscreen.md)
-* [roImageCanvas](https://developer.roku.com/docs/references/brightscript/components/roimagecanvas.md)
-* [roKeyboardScreen](https://developer.roku.com/docs/references/brightscript/components/rokeyboardscreen.md)
-* [roListScreen](https://developer.roku.com/docs/references/brightscript/components/rolistscreen.md)
-* [roMessageDialog](https://developer.roku.com/docs/references/brightscript/components/romessagedialog.md)
-* [roOneLineDialog](https://developer.roku.com/docs/references/brightscript/components/roonelinedialog.md)
-* [roParagraphScreen](https://developer.roku.com/docs/references/brightscript/components/roparagraphscreen.md)
-* [roPinEntryDialog](https://developer.roku.com/docs/references/brightscript/components/ropinentrydialog.md)
-* [roPosterScreen](https://developer.roku.com/docs/references/brightscript/components/roposterscreen.md)
-* [roSearchHistory](https://developer.roku.com/docs/references/brightscript/components/rosearchhistory.md)
-* [roSearchHistory](https://developer.roku.com/docs/references/brightscript/components/rosearchhistory.md)
-* [roSlideShow](https://developer.roku.com/docs/references/brightscript/components/roslideshow.md)
-* [roTextScreen](https://developer.roku.com/docs/references/brightscript/components/rotextscreen.md)
-* [roVideoScreen](https://developer.roku.com/docs/references/brightscript/components/rovideoscreen.md)
-
-# SceneGraph
-Custom SceneGraph components (defined in XML files) are supported, but with a few caveats:
-
-1. The entire chain of `extends="..."` components must be implemented by `brs` or custom nodes.  A missing standard-library node will result in a partially-created component that likely won't function properly.
-2. BrightScript included in a [CDATA](https://en.wikipedia.org/wiki/CDATA) tag is ignored.
-
-Additionally, many components are implemented as "stubs" - their fields exist and are of the correct type, but assigning values to them doesn't trigger any effects (e.g. assigning a string to a `Label`'s `.text` field doesn't change the resulting `.boundingRect()`).  This is intentional for now, as some components are either very complex (e.g. `MarkupGrid`) or would be prohibitively expensive to make accurate (e.g. font rendering nuances that affect layout, wrapping, etc.).  Given that `brs` doesn't intend to implement any sort of graphical rendering, implementing some SceneGraph components as non-stubs would lead to near-meaningless components (what's it mean for an `Video` component to exist if it doesn't play video, advance progress, buffer, etc.?).
-
-| Name | Status | Notes |
-| ---- | ------ | ----- |
-| [AnimationBase](https://developer.roku.com/docs/references/scenegraph/abstract-nodes/animationbase.md) | ⛔ None | Includes all subclasses |
-| [Animation](https://developer.roku.com/docs/references/scenegraph/animation-nodes/animation.md) | ⛔ None | Extends `AnimationBase` |
-| [ArrayGrid](https://developer.roku.com/docs/references/scenegraph/abstract-nodes/arraygrid.md) | ⚠ Stub | |
-| [Audio](https://developer.roku.com/docs/references/scenegraph/media-playback-nodes/audio.md) | ⛔ None | |
-| [BusySpinner](https://developer.roku.com/docs/references/scenegraph/widget-nodes/busyspinner.md) | ⛔ None | |
-| [ButtonGroup](https://developer.roku.com/docs/references/scenegraph/layout-group-nodes/buttongroup.md) | ⛔ None | |
-| [Button](https://developer.roku.com/docs/references/scenegraph/widget-nodes/button.md) | ⛔ None | |
-| [ChannelStore](https://developer.roku.com/docs/references/scenegraph/control-nodes/channelstore.md) | ⛔ None | |
-| [CheckList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/checklist.md) | ⛔ None | |
-| [ColorFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/colorfieldinterpolator.md) | ⛔ None | Extends `AnimationBase` |
-| [ComponentLibrary](https://developer.roku.com/docs/references/scenegraph/control-nodes/componentlibrary.md) | ⚠ Partial | `pkg:/`-local libraries only (as `.zip` files) |
-| [ContentNode](https://developer.roku.com/docs/references/scenegraph/control-nodes/contentnode.md) | ✅ Complete | |
-| [Dialog](https://developer.roku.com/docs/references/scenegraph/dialog-nodes/dialog.md) | ⛔ None | |
-| [FloatFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/floatfieldinterpolator.md) | ⛔ None | Extends `AnimationBase` |
-| [Font](https://developer.roku.com/docs/references/scenegraph/typographic-nodes/font.md) | ⚠ Stub | |
-| [GridPanel](https://developer.roku.com/docs/references/scenegraph/sliding-panels-nodes/gridpanel.md) | ⛔ None | |
-| [Group](https://developer.roku.com/docs/references/scenegraph/layout-group-nodes/group.md) | ⚠ Partial | Fields can be set, but `renderTracking` never gets updated (because nothing renders) |
-| [KeyboardDialog](https://developer.roku.com/docs/references/scenegraph/dialog-nodes/keyboarddialog.md) | ⛔ None | Extends `Dialog` |
-| [Keyboard](https://developer.roku.com/docs/references/scenegraph/widget-nodes/keyboard.md) | ⛔ None | |
-| [LabelList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/labellist.md) | ⛔ None | |
-| [Label](https://developer.roku.com/docs/references/scenegraph/renderable-nodes/label.md) | ⚠ Stub | |
-| [LayoutGroup](https://developer.roku.com/docs/references/scenegraph/layout-group-nodes/layoutgroup.md) | ⚠ Stub | |
-| [ListPanel](https://developer.roku.com/docs/references/scenegraph/sliding-panels-nodes/listpanel.md) | ⛔ None | |
-| [MarkupGrid](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/markupgrid.md) | ⚠ Stub | Assigning `ContentNode` to `.content` field doesn't create items |
-| [MarkupList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/markuplist.md) | ⛔ None | |
-| [MaskGroup](https://developer.roku.com/docs/references/scenegraph/control-nodes/maskgroup.md) | ⛔ None | |
-| [MiniKeyboard](https://developer.roku.com/docs/references/scenegraph/widget-nodes/minikeyboard.md) | ⚠ Stub | |
-| [Node](https://developer.roku.com/docs/references/scenegraph/abstract-nodes/node.md) | ✅ Mostly complete | Some interfaces may not be accurate (e.g. `ifSGNodeBoundingRect`) |
-| [OverhangPanelSetScene](https://developer.roku.com/docs/references/scenegraph/sliding-panels-nodes/overhangpanelsetscene.md) | ⛔ None | |
-| [Overhang](https://developer.roku.com/docs/references/scenegraph/sliding-panels-nodes/overhang.md) | ⛔ None | |
-| [PanelSet](https://developer.roku.com/docs/references/scenegraph/sliding-panels-nodes/panelset.md) | ⛔ None | |
-| [Panel](https://developer.roku.com/docs/references/scenegraph/sliding-panels-nodes/panel.md) | ⛔ None | |
-| [ParallelAnimation](https://developer.roku.com/docs/references/scenegraph/animation-nodes/parallelanimation.md) | ⛔ None | Extends `AnimationBase` |
-| [ParentalControlPinpad](https://developer.roku.com/docs/references/scenegraph/widget-nodes/parentalcontrolpinpad.md) | ⛔ None | |
-| [PinDialog](https://developer.roku.com/docs/references/scenegraph/dialog-nodes/pindialog.md) | ⛔ None | Extends `Dialog` |
-| [Pinpad](https://developer.roku.com/docs/references/scenegraph/widget-nodes/pinpad.md) | ⛔ None | |
-| [PosterGrid](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/postergrid.md) | ⛔ None | |
-| [Poster](https://developer.roku.com/docs/references/scenegraph/renderable-nodes/poster.md) ⚠ Stub | Does not fetch images |
-| [ProgressDialog](https://developer.roku.com/docs/references/scenegraph/dialog-nodes/progressdialog.md) | ⛔ None | Extends `Dialog` |
-| [RadioButtonList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/radiobuttonlist.md) | ⛔ None | |
-| [Rectangle](https://developer.roku.com/docs/references/scenegraph/renderable-nodes/rectangle.md) | ⚠ Stub | |
-| [RowList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/rowlist.md) | ⛔ None | |
-| [Scene](https://developer.roku.com/docs/references/scenegraph/abstract-nodes/scene.md) | ⚠ Stub | |
-| [ScrollableText](https://developer.roku.com/docs/references/scenegraph/typographic-nodes/scrollabletext.md) | ⛔ None | |
-| [ScrollingLabel](https://developer.roku.com/docs/references/scenegraph/typographic-nodes/scrollinglabel.md) | ⛔ None | |
-| [SequentialAnimation](https://developer.roku.com/docs/references/scenegraph/animation-nodes/sequentialanimation.md) | ⛔ None | Extends `AnimationBase` |
-| [SimpleLabel](https://developer.roku.com/docs/references/scenegraph/renderable-nodes/simplelabel.md) | ⛔ None | |
-| [SoundEffect](https://developer.roku.com/docs/references/scenegraph/media-playback-nodes/soundeffect.md) | ⛔ None | |
-| [TargetGroup](https://developer.roku.com/docs/references/scenegraph/layout-group-nodes/targetgroup.md) | ⚠ Stub | |
-| [TargetList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/targetlist.md) | ⛔ None | |
-| [TargetSet](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/targetset.md) | ⛔ None | |
-| [Task](https://developer.roku.com/docs/references/scenegraph/control-nodes/task.md) | ⛔ None | |
-| [TextEditBox](https://developer.roku.com/docs/references/scenegraph/widget-nodes/texteditbox.md) | ⚠ Stub | |
-| [TimeGrid](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/timegrid.md) | ⛔ None | |
-| [Timer](https://developer.roku.com/docs/references/scenegraph/control-nodes/timer.md) | ⚠ Stub | |
-| [Vector2DFieldInterpolator](https://developer.roku.com/docs/references/scenegraph/animation-nodes/vector2dfieldinterpolator.md) | ⛔ None | Extends `AnimationBase` |
-| [Video](https://developer.roku.com/docs/references/scenegraph/media-playback-nodes/video.md) | ⛔ None | |
-| [ZoomRowList](https://developer.roku.com/docs/references/scenegraph/list-and-grid-nodes/zoomrowlist.md) | ⛔ None | |
diff --git a/src/extensions/mockFunction.ts b/src/extensions/mockFunction.ts
index b12217a..191e595 100644
--- a/src/extensions/mockFunction.ts
+++ b/src/extensions/mockFunction.ts
@@ -25,9 +25,10 @@ export class MockFunction extends RoAssociativeArray {
         this.func = func;
 
         this.set(new BrsString("calls"), this.calls);
-        this.set(new BrsString("results"), this.results);
         this.set(new BrsString("clearMock"), this.clearMock);
         this.set(new BrsString("getMockName"), this.getMockName);
+        this.set(new BrsString("mockReturnValue"), this.mockReturnValue);
+        this.set(new BrsString("results"), this.results);
     }
 
     /**
@@ -86,23 +87,86 @@ export class MockFunction extends RoAssociativeArray {
             return this.name;
         },
     });
+
+    /** Overwrites the mock implementation and returns a new value. */
+    private mockReturnValue = new Callable("mockReturnValue", {
+        signature: {
+            args: [new StdlibArgument("returnValue", ValueKind.Dynamic)],
+            returns: ValueKind.String,
+        },
+        impl: (interpreter: Interpreter, returnValue: BrsType) => {
+            this.func = new Callable(
+                this.name.toString(),
+                ...Callable.variadic({
+                    signature: {
+                        args: [],
+                        returns: returnValue.kind,
+                    },
+                    impl: (interpreter: Interpreter, ...args: BrsType[]) => {
+                        return returnValue;
+                    },
+                })
+            );
+
+            interpreter.environment.setMockFunction(
+                this.name.toString(),
+                this.createMockFunction()
+            );
+
+            return BrsInvalid.Instance;
+        },
+    });
 }
 
-export const mockFunction = new Callable("mockFunction", {
-    signature: {
-        args: [
-            new StdlibArgument("functionToMock", ValueKind.String),
-            new StdlibArgument("mock", ValueKind.Callable),
-        ],
-        returns: ValueKind.Invalid,
-    },
-    impl: (interpreter: Interpreter, functionToMock: BrsString, mock: Callable) => {
-        let mockFunctionNode = new MockFunction(functionToMock, mock);
-        interpreter.environment.setMockFunction(
-            functionToMock.toString(),
-            mockFunctionNode.createMockFunction()
-        );
-
-        return mockFunctionNode;
-    },
-});
+export const mockFunction = new Callable(
+    "mockFunction",
+    ...[
+        {
+            signature: {
+                args: [new StdlibArgument("functionToMock", ValueKind.String)],
+                returns: ValueKind.Invalid,
+            },
+            impl: (interpreter: Interpreter, functionToMock: BrsString) => {
+                // Create a function that accepts any number of parameters and returns invalid.
+                let mock = new Callable(
+                    functionToMock.toString(),
+                    ...Callable.variadic({
+                        signature: {
+                            args: [],
+                            returns: ValueKind.Invalid,
+                        },
+                        impl: (interpreter: Interpreter) => {
+                            return BrsInvalid.Instance;
+                        },
+                    })
+                );
+
+                let mockFunctionNode = new MockFunction(functionToMock, mock);
+                interpreter.environment.setMockFunction(
+                    functionToMock.toString(),
+                    mockFunctionNode.createMockFunction()
+                );
+
+                return mockFunctionNode;
+            },
+        },
+        {
+            signature: {
+                args: [
+                    new StdlibArgument("functionToMock", ValueKind.String),
+                    new StdlibArgument("mock", ValueKind.Callable),
+                ],
+                returns: ValueKind.Invalid,
+            },
+            impl: (interpreter: Interpreter, functionToMock: BrsString, mock: Callable) => {
+                let mockFunctionNode = new MockFunction(functionToMock, mock);
+                interpreter.environment.setMockFunction(
+                    functionToMock.toString(),
+                    mockFunctionNode.createMockFunction()
+                );
+
+                return mockFunctionNode;
+            },
+        },
+    ]
+);


Test patch:
diff --git a/test/e2e/BrsMock.test.js b/test/e2e/BrsMock.test.js
index 567cc87..37d0142 100644
--- a/test/e2e/BrsMock.test.js
+++ b/test/e2e/BrsMock.test.js
@@ -80,6 +80,13 @@ describe("e2e/resources/components/mocks", () => {
             "mocked implementation!",
             "0",
             "0",
+            "1234",
+            "foo",
+            "0",
+            "foo",
+            "invalid",
+            "invalid",
+            "mocked!",
         ]);
     });
 
diff --git a/test/e2e/resources/components/mocks/functions/main.brs b/test/e2e/resources/components/mocks/functions/main.brs
index 5318557..1a08839 100644
--- a/test/e2e/resources/components/mocks/functions/main.brs
+++ b/test/e2e/resources/components/mocks/functions/main.brs
@@ -61,6 +61,27 @@ sub main()
     mock.clearMock()
     print mock.calls.count() ' => 0
     print mock.results.count() ' => 0
+
+    mock.mockReturnValue("foo")
+    spyOnMe(1234)
+    print mock.calls[0][0] ' => 1234
+    print mock.results[0] ' => "foo"
+
+    ' Should be able to call with any number of args
+    spyOnMe()
+    print mock.calls[1].count() ' => 0
+    print mock.results[1] ' => "foo"
+
+    ' Implicit mock
+    mock = _brs_.mockFunction("originalHasArgs")
+    ' should work with no args
+    print originalHasArgs() ' => invalid
+
+    ' should work with args that don't match the original signature
+    print originalHasArgs(1, 2, 3, 4, 5) ' => invalid
+
+    mock.mockReturnValue("mocked!")
+    print originalHasArgs(1, 2, 3, 4, 5) ' => mocked!
 end sub
 
 function mockMe()
@@ -74,3 +95,7 @@ end function
 function spyOnMe(arg1 as string, arg2 as integer)
     return "original return value!"
 end function
+
+function originalHasArgs(arg1 as integer, arg2 as string, arg3 as object)
+    return "abcd"
+end function