vendredi 23 février 2018

Python await MagickMock how to mock async functions?

I am trying to become a responsible programmer and test my code. I came up with the following hypotheticals and I am unable to find a way to run all tests using python's built in mock library. I have been unable to get any of the tests to complete properly.....

I am using python 3.6.4

protos.py

#import asyncio #not really necessary here for the example
class resultObject:
    def __init__(self, result, error):
        self.result = result
        self.error = error
        self.ran = 1

    async def get_results(self):
        #await asyncio.sleep(0.1) #not really necessary for the example
        return self.result, self.error

async def function_to_mock(instruction):
        if not isinstance(instruction, str):
            raise RuntimeError("Instruction is not a string") #inner error

        if " " in instruction:
            error = "Instruction contains a space"
        else:
            error = None

        result = f"Instruction: {instruction} was run"
        #await asyncio.sleep(0.1) #not really necessary for the example
        return resultObject(result, error)

async def function_to_test(instruction):
    output = await function_to_mock(instruction)
    result, error = await output.get_results()
    print(output.ran)

    if error: #outer error
        raise RuntimeError("No Spaces Allowed")

    return result

mytest.py

import unittest
from unittest.mock import patch
from unittest.mock import MagicMock

from protos import function_to_test

class tester(unittest.TestCase):
    def setUp(self):
        import asyncio
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(None)

    def tearDown(self):
        self.loop.close()

    def test_valid_instruction(self, instruction = "valid_instruction"):
        with patch("protos.function_to_mock", autospec=True, return_value=MagicMock(result="Instruction: valid_instruction was run", error=None)) as MockFunc:
             result = self.loop.run_until_complete(function_to_test(instruction))
        MockFunc.assert_called_once_with(instruction)
        self.assertEqual(result, "Instruction: valid_instruction was run" )

    def test_invalid_instruction(self, instruction=["invalid_instruction"]):
        with patch("protos.function_to_mock", autospec=True, sideffect=RuntimeError("Instruction is not a string")) as MockFunc:
             with self.assertRaises(RuntimeError) as cm:
                self.loop.run_until_complete(function_to_test(instruction))
        e = cm.exception
        self.assertEqual(e.args[0], "Instruction is not a string")
        MockFunc.assert_called_once_with(instruction)

    def test_spacein_instruction(self, instruction = "in valid_instruction"):
        with patch("protos.function_to_mock", autospec=True, return_value=MagicMock(result="Instruction: in valid_instruction was run", error="Instruction contains a space")) as MockFunc:
            with self.assertRaises(RuntimeError) as cm:
                result =  self.loop.run_until_complete(function_to_test(instruction))
        e = cm.exception
        self.assertEqual(e.args[0], "No Spaces Allowed")  
        MockFunc.assert_called_once_with(instruction)


if __name__ == '__main__':


    unittest.main()

Output

python3.6 mytests.py
.
.
.
TypeError: object MagicMock can't be used in 'await' expression

Explanation

I have a function that runs an instruction. There are three cases, (i) a valid instruction (i.e. string with no spaces), (ii) an invalid instruction type where the type of the instruction is wrong (i.e. not a string), and (iii) finally an invalid instruction (i.e. an instruction that has a space in it). This is a "simplified" hypothetical approximation. The issue seems to be that it can't await the MagicMock object and I have not found a satisfactory way to work around it.

Aucun commentaire:

Enregistrer un commentaire