Lyrebird is a small module designed to make testing code easier.
Heavily based off of Python’s unittest.mock
library, it uses getfenv
and setfenv
to allow for monkey patching of globals to minimise the amount of modification needed in order to test code.
This module cannot run outside of studio
When will this be useful?
A large portion of debugging is testing your code against edge cases that are hard to spot.
Say you have a script that handles loading and saving data. What would happen if two players joined or left at the exact same time? You may try to test it by having an alt join at the exact same time as another account but it's difficult to coordinate on top of difficult to diagnose issues.
Instead, you can opt to mock the PlayerAdded event so as to test your function for that specific edge case in a repeatable and convenient manner.
How do I use it?
Lyrebird has 3 methods that once called, subsequent calls made to the original functions are automatically redirected to use the patched function. Any further calls to these 3 methods will add to the pre-existing mocked environment.Mock
Mock(PathToFunction:string, ReturnValues:{any}, Depth:number)
Used to patch a function given as a string with a fixed set of return values given as an array. Optionally, depth can be provided to specify what layer of the function stack to apply the patch to.
Code samples
Patching math.random
with a custom function that returns 1, 2, 3, 4, 5
each time the function is called can be done like so
local lyrebird = require(game.ReplicatedStorage.Lyrebird)
lyrebird.Mock("math.random", {1,2,3,4,5})
for i = 1,5 do
print(math.random(10))
end
MockService
MockService(ServiceName:string, Depth:number)
Provides an interface to users to patch over a service given as a string and returns a table used to populate the mocked service with events that can be manually fired. Optionally, depth can be provided to specify what layer of the function stack to apply the patch to.
Mocking events and functions of a service
The table returned by MockService provides two functions to facilitate mocking events and functions- If EventName is a valid event of the service, patches over the event of the service with a BindableEvent
- If FunctionName is a valid function of the service, patches over the function of the service with FunctionToReplace
MockEvent(EventName:string)
MockFunction(FunctionName:string, FunctionToReplace:function)
Code samples
Patching UserInputService
and mocking the event InputBegan
:
local MockUIS = lyrebird.MockService("UserInputService")
MockUIS:MockEvent("InputBegan")
--Connect listeners
game:GetService("UserInputService").InputBegan:Connect(function(InputObject, Processed)
end)
--Fire the InputBegan function with custom data
MockUIS.InputBegan:Fire({KeyCode = Enum.KeyCode.Return})
Patching DataStoreService
and mocking the method GetDataStore
:
local MockDSS = lyrebird.MockService("DataStoreService")
MockDSS:MockFunction("GetDataStore", function(self, DataStoreName) return {["FakeKey"]=FakeData} end)
Reset
Reset ()
Resets the environment the script is running in to its default
Code samples
local lyrebird = require(game.ReplicatedStorage.Lyrebird)
lyrebird.Mock("math.random",{1,2,3,4,5})
for i = 1, 5 do
print(math.random(10))
end
lyrebird.Reset()
Why getfenv/setfenv?
getfenv and setfenv are a problematic pair of functions that interfere with luau and are just bad practice in general.However, I am unaware of any other method of allowing for patching functions in a non-invasive manner. As such, this module is only available in studio to curb any bad practices before a codebase becomes dependent on it.
What's next?
I don't intend to expand this module beyond increasing the functionality of the three methods.
That being said, I plan on creating more modules to work in tandem with Lyrebird to make more robust methods of testing code easier.