0

I'm trying to mock a multiprocessing.Event object so that a call to event.is_set() returns False on the first iteration and true on the second iteration. I've failed at the following attempt:

import unittest
import unittest.mock as mock
import multiprocessing


class MyClassUnderTest:
    def __init__(self):
        self.event = multiprocessing.Event()
        # start a Process which immediately uses self.event

    def my_method_under_test(self):
        while not self.event.is_set():
            pass  # do something
        return True


class TestMyClassUnderTest(unittest.TestCase):
    @mock.patch.object(multiprocessing.Event, 'is_set', return_value=False)
    def test_my_method(self, mock_event):
        mock_event.side_effect = [False, True]

        myobj = MyClassUnderTest()
        self.assertTrue(myobj.my_method_under_test())

I end up with the error:

AttributeError: <bound method BaseContext.Event of <multiprocessing.context.DefaultContext object at 0x7f1068a50e48>> does not have the attribute 'is_set'

I cannot override MyClassUnderTest.event because the event object is used immediately, so I'm trying to override the entire multiprocessing.Event class.

David Parks
  • 30,789
  • 47
  • 185
  • 328

2 Answers2

1

Here's a way to do it with configure_mock:

import unittest
import unittest.mock as mock
import multiprocessing


class MyClassUnderTest:
    def __init__(self):
        self.event = multiprocessing.Event()
        # start a Process which immediately uses self.event

    def my_method_under_test(self):
        while not self.event.is_set():
            pass  # do something
        return True


class TestMyClassUnderTest(unittest.TestCase):
    @mock.patch('multiprocessing.Event')
    def test_my_method(self, mock_event):
        mock_event.configure_mock(**{'is_set.side_effect': [False, True]})

        myobj = MyClassUnderTest()
        self.assertTrue(myobj.my_method_under_test())

If anyone knows how to condense this entirely into @mock.patch(...) that would still be useful.

David Parks
  • 30,789
  • 47
  • 185
  • 328
1

To respond the call by @David Parks, here is a simplified working example.

import unittest
import unittest.mock as mock
import multiprocessing


class MyClassUnderTest:
    def __init__(self):
        self.event = multiprocessing.Event()
        # start a Process which immediately uses self.event

    def my_method_under_test(self):
        while not self.event.is_set():
            pass  # do something
        return True


class TestMyClassUnderTest(unittest.TestCase):
    @mock.patch('multiprocessing.Event')
    def test_my_method(self, mock_event):
        mock_event.is_set.side_effect = [False, True]

        myobj = MyClassUnderTest()
        self.assertTrue(myobj.my_method_under_test())

The trick is to patch the Event class in the decorator only, and mock the response of is_set() in the test body. Honestly, I don't know why we cannot directly patch multiprocessing.Event.is_set, given that multiprocessing.Event is a clone of threading.Event.

Fanchen Bao
  • 3,310
  • 1
  • 21
  • 34