Calling custom actions in Python unittest

Hi all,

A short disclaimer: I am new to Python and have very little professional programming experience. So suspect that my question will be quite silly for someone who knows what they are doing.

This question is close to what has been asked here: How do you test Custom Actions before using them with a bot? - unfortunately I am not really able to translate what is done in rasa/test_actions.py at master · RasaHQ/rasa · GitHub to my attempt of using unittest instead of pytest.

Consider the following minimal example:

$ cat actions/actions.py

class ActionFoo(Action):
    def name(self):
        return "action_foo"

    def run(self, dispatcher, tracker, domain):
        // do business logic and send a response
        dispatcher.utter_message(message)
        return []
$ cat actions/test_actions.py

import unittest

from rasa_sdk import Action, Tracker
from rasa_sdk.executor import CollectingDispatcher
from rasa_sdk.types import DomainDict

import actions
from actions import (
    ActionFoo,
)

class TestActionSearchRestaurant(unittest.TestCase):

    def testAction(self):
        ActionFoo.run(self, CollectingDispatcher(), Tracker, DomainDict)

if __name__ == '__main__':
    unittest.main()

I am stuck at the part where I can actually call the ActionFoo classes “run” function and check if the output is expected. I know that the function accepts “(self, dispatcher, tracker, domain)”, but I am struggling to “mock” those in my Python test. Right now, I don’t get any errors, but no message back either:

$ python actions/test_actions.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

I assume that one needs to fill the Tracker, Domain etc. with mock data to simulate user interaction, but I don’t quite get it.

Where is my mistake / false assumption /etc.?

Thanks!

Firstly, since run is not a class method, you will need to first create an ActionFoo instance before you can call the run function, ie. ActionFoo().run(...), and you also don’t want to pass self as the first argument, otherwise in run the self will refer to an instance of TestActionSearchRestaurant, not an instance of ActionFoo.

The Tracker and Domain you can create similarly to the way you’re creating the CollectingDispatcher, but they’ll need some arguments, ie. Tracker(.....)

But it seems like you’re not using the tracker or domain in your run function, so you can just pass in None instead as a quick way of using those tests, ie. ActionFoo().run(CollectingDispatcher(), None, None)

Thanks @rudi, this makes it a bit more clear. My initial idea of testing was to check if the output of ActionFoo (sent via dispatcher.utter_message(message)) by wrapping it in self.assertEqual, like so:

class TestActionSearchRestaurant(unittest.TestCase):

    def testAction(self):
        self.assertEqual(ActionFoo().run(CollectingDispatcher(), None, None), "The output should be this string.")

However, the output of the ActionFoo call is [] and not a string (as it is when I engage with my chat assistant when it is deployed).

So the run function just returns an empty list return [], so that’s expected.

The message is going to the dispatcher, so that’s probably where you want to look.

dispatcher = CollectingDispatcher()
ActionFoo.run(dispatcher, None, None)
self.assertEqual(dispatcher.messages, [....])

Thank you very very much, this did the trick!

What about tracker ?

Can use mock like this : @pytest.fixture

def Simulation_data():

return {"simulation_credit_name":[{"simulation_id":"1232326454"}]}

@pytest.mark.parametrize([“selected_slot_name”,“selected_id”],[(“simulation_credit_name”,“1232326454”)])

@mock.patch(“rasa_sdk.interfaces.Tracker”) def test_getselectedsimulation_simulationdetail_returnemptylist(mock_Tracker,selected_slot_name:str,selected_id:str ,Simulation_data):

##Arange
mock_Tracker.slots.return_value=Simulation_data
mock_Tracker.get_slot =Tracker.get_slot
print(mock_Tracker.slots)
Get_selected_simulation(mock_Tracker,selected_id,selected_slot_name,""

You don’t need to do a mock.patch on the tracker. You can just create a tracker object, and pass it as the second parameter to run, for example: healthcheckbot/test_forms.py at master · praekeltfoundation/healthcheckbot · GitHub