Writing unit tests for your AO processes using the Test module

Developing your decentralised applications on Arweave ao is simple, all you have to do is, create variables and functions to store and modify your data, then create handlers which will handle the incoming messages and call the defined functions accordingly :)

But writing backend logic, that too for a radically new environment using a language that you might never have programmed in before, can be a daunting task. Moreover, you would not want there to be any loopholes in your code right?

Well, fear not because in this guide we will be looking at how you can write unit tests for your functions using the unit testing module by Tom.

First, a sneak peek!

At the end of this article you will be able to create and run unit tests and get the results just the this screenshot:

final outcome
final outcome

Development setup

We will be using BetterIDEa, which is an online IDE specialised for AO, as our development environment. Here you can easily install packages and write code in its notebook like ui

To get started, visit ide.betteridea.dev and connect your web wallet

ide.betteridea.dev home page
ide.betteridea.dev home page

Once connected, create a new project and give it a name you want. I’ll name it ‘how-to-unit-test‘. Next we need to tell the IDE which process it should run the code in, you can choose a previously created process or spawn a new one. I will create a new one and name it ‘unit-tester‘

New project dialog
New project dialog

And that’s it, hit create project and within seconds you’ll have your notebook up and running. Try running the default code and you should get an output saying “Hello AO!“

IDE Notebook ui
IDE Notebook ui

Let’s write some LUA!

We’ll start simple, by creating a chatroom 🤭

Feel free to follow along and create multiple cells to write and run the different functions we will be making

  1. Initialise a chatroom table

  2. Function to register a nickname

  3. Function to send a message

  4. Function to get all messages

Initialise a chatroom table

Chatroom = {
    messages = {},
    users = {}
}
return Chatroom

Write the above code in a code cell and run it. You should get an output that prints an empty table with empty messages and users fields.

Function to register nickname

function setNickname(userAddress, nickname)
    Chatroom.users[userAddress] = nickname
end


This code snippet adds an entry to the Chatroom.users field to store their nickname

Function to send a message

function sendMessage(from, message, timestamp)
    local nickname = Chatroom.users[from] or ""
    local msg = {
        from = from,
        nickname = nickname,
        timestamp = timestamp,
        message = message
    }
    table.insert(Chatroom.messages, msg)
end

This code snippet checks if the user has a nickname, and appends message data to the Chatroom.messages table

Function to get all messages

function getMessages()
    return Chatroom.messages
end

This function just returns all of the messages that have been sent in the chatroom

Verify the code works as expected

To check if these functions that we have defined work, let’s try running each of them one by one and see if they work.

Starting with setNickname, if we pass our process id and a string, it should add an entry to Chatroom.users

result of using setNickname()
result of using setNickname()

Next we try running the sendMessage function and pass our process id, the message and a timestamp (0 for testing), and can see that an item has been added to Chatroom.messages

sendMessage()
sendMessage()

lastly, getMessages also works as expected

getMessages()
getMessages()

But, wait a minute… What if the user passes something other than the expected values?

Fear not, we are going to add exactly that now :)

For adding checks, we will use the assert function that lua provides, to make sure our conditions are met, else an error gets thrown

sample usage
sample usage
assert(condition, "error message if condition is false")

and we can put anything in condition, for example:

  • whole variables to check if they have been defined

  • size of tables or strings

  • comparisons between variables

Some checkers we can add to our chatroom app are:

  1. Check if message is a string

  2. Check if size of ao.id is 43 (standard process id size)

  3. If timestamp is a number and nothing else

  4. If nickname has at least 3 characters

assert(type(userAddress) == "string", "user address should be a string")
assert(#userAddress == 43, "invalid user address size")
assert(#nickname >= 3, "nickname should be atleast 3 characters long")
assert(type(message) == "string", "message should be a string")
assert(#from == 43, "invalid process id size")
assert(type(timestamp) == "number", "timestamp should be a number")

Add the given snippets to the start of setNickname and sendMessage functions.

Now if we call these functions with some unwanted data, it should throw errors like this

errors thrown by the assert functions
errors thrown by the assert functions

Use the Test module to run automated unit tests

Head over to the top right on the IDE and click on the Modules icon. A dialog box should open, asking you which module to load, simply select Test and hit load.
(feel free to inspect the code that will be loaded on your process)

Loading the Test module
Loading the Test module

Now we can import the module using the require function.

Lets try running the example code from the repos example.lua file

local Test = require("Test")
local myTests = Test.new('example tests')
myTests:add("ok", function () assert(true, 'passing test') end)
return myTests:run()
example tests
example tests

And as you can see it runs the tests and prints the results for passed and failed cases

Lets create a tests for our chatroom

local Test = require("Test")
chatTests = Test.new('Chatroom unit tests')
return chatTests

Run this in a module and have a look at the result

tests table
tests table

Let’s create and add tests for our functions.
To create a test, wrap your intended function around another function() … end block and pass it to the chatTests:add() function along with a name for this test

Like this 👇

chatTests:add("verify table", function()
    assert(type(Chatroom)=="table", "Chatroom is not a table")
end)
chatTests:add("verify users table", function()
    assert(type(Chatroom.users)=="table", "Chatroom.users is not a table")
end)
chatTests:add("verify messages table", function()
    assert(type(Chatroom.messages)=="table", "Chatroom.messages is not a table")
end)

To check the sendMessage function

Chatroom.messages = {}
chatTests:add("send message", function()
    sendMessage(ao.id, "hi", 0)
end)
chatTests:add("verify message sent:1", function() 
    assert(#Chatroom.messages == 1, "Message was not logged")
end)
chatTests:add("verify message sent:1", function()
    assert(Chatroom.messages[1].message == "hi", "Message content is different") 
end)

These tests send a message, then check in the Chatroom.messages table wether the message data was added accurately.

Lastly add tests for checking the setNickname function, 2 of which are set to fail deliberately

chatTests:add("set nick", function()
    setNickname('O1d7qSBUEyDMBd8JuljtGo2vPhX-LFn5DUEObX2GCMc','ankush')
end)
chatTests:add("set nick", function()
    setNickname('7quc6o_k8O2DA0YD0sqZ0brsTVFhZVaRznNZ6KxahmY','weeblet')
end)
chatTests:add("set nick:fail", function()
    setNickname('illegal-id','weeblet')
end)
chatTests:add("set nick:fail", function()
    setNickname(ao.id,'aa')
end)


Feel free to add our own tests too 💡

Then finally we run the tests and look at the results

test results
test results

Wasn’t that simple 🤩


You can access the full notebook here 👇

For any questions or if you need help, feel free to join the BetterIDEa discord community

Thank you for reading ;)

Subscribe to BetterIDEa
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.