Repository: ivancrneto/pymox

Issue:
Add string imports
Right now this can be done:
```python
class TestOs:
     def test_getcwd(self):
        with stubout(os, 'getcwd') as m_getcwd, expect:
            m_getcwd.to_be.called_with().and_return('/mox/path')

        assert os.getcwd() == '/mox/path'
        mox.verify(m_getcwd)
```

we need to be able to do:
```python
class TestOs:
     def test_getcwd(self):
        with stubout('os.getcwd') as m_getcwd, expect:
            m_getcwd.to_be.called_with().and_return('/mox/path')

        assert os.getcwd() == '/mox/path'
        mox.verify(m_getcwd)
```

Discussion:

Patch:
diff --git a/mox/contextmanagers.py b/mox/contextmanagers.py
index 9d732e0..4291734 100644
--- a/mox/contextmanagers.py
+++ b/mox/contextmanagers.py
@@ -31,7 +31,19 @@ class Stubout:
             method = getattr(self.m, "stubout")
 
         for stub_args in self.stubs:
-            obj, attr_name, *use_mock_anything = stub_args
+            if len(stub_args) >= 3:
+                obj, attr_name, *use_mock_anything = stub_args
+            elif len(stub_args) == 2:
+                if isinstance(stub_args[0], str):
+                    obj = stub_args[0]
+                    attr_name = None
+                    use_mock_anything = stub_args[1]
+                else:
+                    obj, attr_name, *use_mock_anything = stub_args
+            else:
+                obj = stub_args[0]
+                attr_name = None
+                use_mock_anything = False
             kwargs = {} if self._class else {"use_mock_anything": bool(use_mock_anything)}
 
             stub_obj = method(obj=obj, attr_name=attr_name, **kwargs)
diff --git a/mox/exceptions.py b/mox/exceptions.py
index ce351b1..089e7e0 100644
--- a/mox/exceptions.py
+++ b/mox/exceptions.py
@@ -2,6 +2,12 @@
 import difflib
 
 
+class ObjectResolutionError(TypeError):
+    def __init__(self, path):
+        message = f"Could not resolve class, object or module from supplied reference: {path!r}"
+        super().__init__(message)
+
+
 class Error(AssertionError):
     """Base exception for this module."""
 
diff --git a/mox/helpers.py b/mox/helpers.py
new file mode 100644
index 0000000..7669e17
--- /dev/null
+++ b/mox/helpers.py
@@ -0,0 +1,132 @@
+# Python imports
+import pkgutil
+import sys
+from functools import partial, wraps
+
+# Internal imports
+from mox.exceptions import ObjectResolutionError
+
+
+_NAME_PATTERN = None
+
+
+def _resolve_name(name):  # pragma: no cover
+    """
+    Copied from pkg_util.resolve_name so it can be used with Python 3.8
+
+    Resolve a name to an object.
+
+    It is expected that `name` will be a string in one of the following
+    formats, where W is shorthand for a valid Python identifier and dot stands
+    for a literal period in these pseudo-regexes:
+
+    W(.W)*
+    W(.W)*:(W(.W)*)?
+
+    The first form is intended for backward compatibility only. It assumes that
+    some part of the dotted name is a package, and the rest is an object
+    somewhere within that package, possibly nested inside other objects.
+    Because the place where the package stops and the object hierarchy starts
+    can't be inferred by inspection, repeated attempts to import must be done
+    with this form.
+
+    In the second form, the caller makes the division point clear through the
+    provision of a single colon: the dotted name to the left of the colon is a
+    package to be imported, and the dotted name to the right is the object
+    hierarchy within that package. Only one import is needed in this form. If
+    it ends with the colon, then a module object is returned.
+
+    The function will return an object (which might be a module), or raise one
+    of the following exceptions:
+
+    ValueError - if `name` isn't in a recognised format
+    ImportError - if an import failed when it shouldn't have
+    AttributeError - if a failure occurred when traversing the object hierarchy
+                     within the imported package to get to the desired object.
+    """
+    # Python imports
+    import importlib
+
+    global _NAME_PATTERN
+    if _NAME_PATTERN is None:
+        # Lazy import to speedup Python startup time
+        # Python imports
+        import re
+
+        dotted_words = r"(?!\d)(\w+)(\.(?!\d)(\w+))*"
+        _NAME_PATTERN = re.compile(f"^(?P<pkg>{dotted_words})" f"(?P<cln>:(?P<obj>{dotted_words})?)?$", re.UNICODE)
+
+    m = _NAME_PATTERN.match(name)
+    if not m:
+        raise ValueError(f"invalid format: {name!r}")
+    gd = m.groupdict()
+    if gd.get("cln"):
+        # there is a colon - a one-step import is all that's needed
+        mod = importlib.import_module(gd["pkg"])
+        parts = gd.get("obj")
+        parts = parts.split(".") if parts else []
+    else:
+        # no colon - have to iterate to find the package boundary
+        parts = name.split(".")
+        modname = parts.pop(0)
+        # first part *must* be a module/package.
+        mod = importlib.import_module(modname)
+        while parts:
+            p = parts[0]
+            s = f"{modname}.{p}"
+            try:
+                mod = importlib.import_module(s)
+                parts.pop(0)
+                modname = s
+            except ImportError:
+                break
+    # if we reach this point, mod is the module, already imported, and
+    # parts is the list of parts in the object hierarchy to be traversed, or
+    # an empty list if just the module is wanted.
+    result = mod
+    for p in parts:
+        result = getattr(result, p)
+    return result
+
+
+if sys.version_info < (3, 9):
+    resolve_name = _resolve_name
+else:
+    resolve_name = pkgutil.resolve_name
+
+
+def resolve_object(func):
+    """Resolves an object and its attribute before calling the function in case a reference/path is pased."""
+
+    def import_object(path):
+        try:
+            obj, attribute = path.rsplit(".", 1)
+        except (TypeError, ValueError, AttributeError):
+            raise ObjectResolutionError(path)
+
+        return partial(resolve_name, obj), attribute
+
+    @wraps(func)
+    def wrapper(self, *args, **kwargs):
+        obj, attr_name = None, None
+
+        if len(args) >= 1:
+            obj = args[0]
+
+        if len(args) >= 2:
+            attr_name = args[1]
+
+        if "obj" in kwargs:
+            obj = kwargs.pop("obj")
+        if "attr_name" in kwargs:
+            attr_name = kwargs.pop("attr_name")
+
+        if isinstance(obj, str):
+            path = f"{obj.attr_name}" if attr_name else obj
+            obj, attr_name = import_object(path)
+            obj = obj()
+
+        result = func(self, obj, attr_name, *args[2:], **kwargs)
+        return result
+
+    return wrapper
diff --git a/mox/mox.py b/mox/mox.py
index b11009b..0ff1d1e 100755
--- a/mox/mox.py
+++ b/mox/mox.py
@@ -80,6 +80,7 @@ from .exceptions import (
     UnknownMethodCallError,
 )
 from .groups import MethodGroup, MultipleTimesGroup, UnorderedGroup
+from .helpers import resolve_object
 
 
 class _MoxManagerMeta(type):
@@ -202,6 +203,7 @@ class Mox(metaclass=_MoxManagerMeta):
         for mock_obj in self._mock_objects:
             mock_obj._reset()
 
+    @resolve_object
     def stubout(self, obj, attr_name, use_mock_anything=False):
         """Replace a method, attribute, etc. with a Mock.
 
@@ -238,6 +240,7 @@ class Mox(metaclass=_MoxManagerMeta):
         self.stubs.set(obj, attr_name, stub)
         return stub
 
+    @resolve_object
     def stubout_class(self, obj, attr_name):
         """Replace a class with a "mock factory" that will create mock objects.
 
diff --git a/pyproject.toml b/pyproject.toml
index 9556775..9749b52 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -66,8 +66,10 @@ addopts = [
     "--cov=mox",
     "--cov-branch",
     "--cov-report=xml",
+    "--ignore-glob=*_helper.py",
     "test"
 ]
+python_files = "*test*.py"
 
 
 [tool.black]


Test patch:
diff --git a/test/mox_test.py b/test/mox_test.py
index 2f6939b..074c920 100755
--- a/test/mox_test.py
+++ b/test/mox_test.py
@@ -3368,6 +3368,95 @@ class MoxContextManagerTest:
         assert "foo" == actual
         assert type(test_obj.other_valid_call) is method_type
 
+    def test_stub_out_many_method_another_object(self):
+        """Test that a method is replaced with a MockObject when stubout.many is used."""
+        from .mox_test_helper import TestClass
+
+        test_obj = TestClass(parent=TestClass())
+        test_obj.another_parent = TestClass()
+
+        method_type = type(test_obj.valid_call)
+        method_type_other = type(test_obj.other_valid_call)
+        with mox.stubout.many(
+            ["test.mox_test_helper.TestClass.valid_call", True],
+            ["test.mox_test_helper.TestClass.other_valid_call", True],
+        ) as (mock_valid, mock_other_valid), mox.expect:
+            mock_valid().returns("foo")
+            mock_other_valid().returns("bar")
+
+        assert isinstance(test_obj.valid_call, mox.MockAnything)
+        assert isinstance(test_obj.other_valid_call, mox.MockAnything)
+        assert type(test_obj.valid_call) is not method_type
+        assert type(test_obj.other_valid_call) is not method_type_other
+
+        actual_parent = test_obj.parent.valid_call()
+        actual_another_parent = test_obj.parent.other_valid_call()
+
+        mox.verify(mock_valid, mock_other_valid)
+
+        mox.Mox.unset_stubs_for_id(mock_valid._mox_id)
+        mox.Mox.unset_stubs_for_id(mock_other_valid._mox_id)
+        assert actual_parent == "foo"
+        assert actual_another_parent == "bar"
+        assert type(test_obj.parent.valid_call) is method_type
+        assert type(test_obj.parent.valid_call) is method_type_other
+
+    def test_stub_out_method_another_object_not_use_mock_anything(self):
+        """Test that a method is replaced with a MockObject when not using mock anything."""
+        test_obj = TestClass(parent=TestClass())
+        method_type = type(test_obj.parent.valid_call)
+        with mox.stubout(test_obj, "parent") as stub:
+            ...
+
+        assert isinstance(test_obj.parent.valid_call, mox.MockMethod)
+        assert type(test_obj.parent.valid_call) is not method_type
+
+        with pytest.raises(
+            mox.exceptions.UnknownMethodCallError, match="Method called is not a member of the object: non_existing"
+        ):
+            _ = test_obj.parent.non_existing
+
+        with stub._expect:
+            test_obj.parent.valid_call().returns("foo")
+
+        actual = test_obj.parent.valid_call()
+
+        with pytest.raises(
+            mox.exceptions.SwallowedExceptionError, match="Method called is not a member of the object: non_existing"
+        ):
+            mox.verify(stub)
+
+        mox.Mox.unset_stubs_for_id(stub._mox_id)
+        assert actual == "foo"
+        assert type(test_obj.parent.valid_call) is method_type
+
+    def test_stub_out_method_another_object_use_mock_anything(self):
+        """Test that a method is replaced with a MockMethod when using mock anything."""
+        test_obj = TestClass(parent=TestClass())
+        method_type = type(test_obj.parent.valid_call)
+        with mox.stubout(test_obj, "parent", True) as stub:
+            ...
+
+        assert isinstance(test_obj.parent.valid_call, mox.MockMethod)
+        assert isinstance(test_obj.parent.non_existing, mox.MockMethod)
+        assert type(test_obj.parent.valid_call) is not method_type
+
+        with stub._expect:
+            test_obj.parent.valid_call().returns("foo")
+            test_obj.parent.non_existing().returns("now it exists")
+
+        actual = test_obj.parent.valid_call()
+        exists = test_obj.parent.non_existing()
+
+        mox.verify(stub)
+
+        mox.Mox.unset_stubs_for_id(stub._mox_id)
+        assert actual == "foo"
+        assert exists == "now it exists"
+        assert type(test_obj.parent.valid_call) is method_type
+        with pytest.raises(AttributeError):
+            _ = test_obj.parent.non_existing
+
     def test_stub_out_method__unbound__comparator(self):
         instance = TestClass()
         with mox.stubout(TestClass, "other_valid_call") as stub, mox.expect:
@@ -4379,8 +4468,9 @@ class TestClass:
     SOME_CLASS_VAR = "test_value"
     _PROTECTED_CLASS_VAR = "protected value"
 
-    def __init__(self, ivar=None):
+    def __init__(self, ivar=None, parent=None):
         self.__ivar = ivar
+        self.parent = parent
 
     def __eq__(self, rhs):
         return self.__ivar == rhs
diff --git a/test/mox_test_helper.py b/test/mox_test_helper.py
index ef20d8c..f10cfb8 100755
--- a/test/mox_test_helper.py
+++ b/test/mox_test_helper.py
@@ -27,6 +27,7 @@ See mox_test.MoxTestBaseTest for how this class is actually used.
 # Python imports
 import abc
 import os
+import re
 
 # Internal imports
 import mox
@@ -48,6 +49,8 @@ class ExampleMoxTestMixin(object):
 
 
 class ExampleMoxTest(mox.MoxTestBase, ExampleMoxTestMixin):
+    __test__ = False
+
     DIR_PATH = "/path/to/some/directory"
 
     def test_success(self):
@@ -211,3 +214,112 @@ class SpecialClass(object):
 class ChildExampleClass(ExampleClass):
     def __init__(self):
         ExampleClass.__init__(self)
+
+
+class TestClass:
+    """This class is used only for testing the mock framework"""
+
+    SOME_CLASS_SET = {"a", "b", "c"}
+    SOME_CLASS_VAR = "test_value"
+    _PROTECTED_CLASS_VAR = "protected value"
+
+    def __init__(self, ivar=None, parent=None):
+        self.__ivar = ivar
+        self.parent = parent
+
+    def __eq__(self, rhs):
+        return self.__ivar == rhs
+
+    def __ne__(self, rhs):
+        return not self.__eq__(rhs)
+
+    def valid_call(self):
+        pass
+
+    def method_with_args(self, one, two, nine=None):
+        pass
+
+    def other_valid_call(self):
+        pass
+
+    def optional_args(self, foo="boom"):
+        pass
+
+    def valid_call_with_args(self, *args, **kwargs):
+        pass
+
+    @classmethod
+    def my_class_method(cls):
+        pass
+
+    @staticmethod
+    def my_static_method():
+        pass
+
+    def _protected_call(self):
+        pass
+
+    def __private_call(self):
+        pass
+
+    def __do_not_mock(self):
+        pass
+
+    def __getitem__(self, key):
+        """Return the value for key."""
+        return self.d[key]
+
+    def __setitem__(self, key, value):
+        """Set the value for key to value."""
+        self.d[key] = value
+
+    def __contains__(self, key):
+        """Returns True if d contains the key."""
+        return key in self.d
+
+    def __iter__(self):
+        pass
+
+    def re_search(self):
+        return re.search("a", "ivan")
+
+
+class ChildClass(TestClass):
+    """This inherits from TestClass."""
+
+    def __init__(self):
+        TestClass.__init__(self)
+
+    def child_valid_call(self):
+        pass
+
+
+class SimpleCallableClass(object):
+    """This class is callable, and that should be mockable!"""
+
+    def __init__(self):
+        pass
+
+    def __call__(self, param):
+        return param
+
+
+class ClassWithProperties(object):
+    def setter_attr(self, value):
+        pass
+
+    def getter_attr(self):
+        pass
+
+    prop_attr = property(getter_attr, setter_attr)
+
+
+class SubscribtableNonIterableClass(object):
+    def __getitem__(self, index):
+        raise IndexError
+
+
+class InheritsFromCallable(SimpleCallableClass):
+    """This class should also be mockable; it inherits from a callable class."""
+
+    pass
diff --git a/test/mox_test_string_imports.py b/test/mox_test_string_imports.py
new file mode 100644
index 0000000..882df67
--- /dev/null
+++ b/test/mox_test_string_imports.py
@@ -0,0 +1,829 @@
+# Python imports
+import re
+
+# Pip imports
+import pytest
+
+# Internal imports
+import mox
+
+from . import mox_test_helper
+from .test_helpers.subpackage.faraway import FarAwayClass
+
+
+@pytest.fixture
+def mock():
+    return mox.Mox()
+
+
+class TestStringimports:
+    def test_existing_function(self):
+        with mox.stubout("test.test_helpers.subpackage.faraway.FarAwayClass.distant_method") as mock_distant_method:
+            mock_distant_method().returns(True)
+
+        obj = FarAwayClass()
+        assert obj.distant_method() is True
+
+    def test_missing_function(self):
+        with pytest.raises(AttributeError, match="type object 'FarAwayClass' has no attribute 'missing_method'"):
+            with mox.stubout("test.test_helpers.subpackage.faraway.FarAwayClass.missing_method") as mock_distant_method:
+                mock_distant_method().returns(True)
+
+    def test_missing_class(self):
+        with pytest.raises(
+            AttributeError, match="module 'test.test_helpers.subpackage.faraway' has no attribute 'MissingClass'"
+        ):
+            with mox.stubout("test.test_helpers.subpackage.faraway.MissingClass.distant_method") as mock_distant_method:
+                mock_distant_method().returns(True)
+
+    def test_missing_module(self):
+        with pytest.raises(AttributeError, match="module 'test.test_helpers.subpackage' has no attribute 'missing'"):
+            with mox.stubout("test.test_helpers.subpackage.missing.FarAwayClass.distant_method") as mock_distant_method:
+                mock_distant_method().returns(True)
+
+    def test_missing_package(self):
+        with pytest.raises(AttributeError, match="module 'test.test_helpers' has no attribute 'missing_package'"):
+            with mox.stubout(
+                "test.test_helpers.missing_package.faraway.FarAwayClass.distant_method"
+            ) as mock_distant_method:
+                mock_distant_method().returns(True)
+
+    def test_object_resolution_error(self):
+        with pytest.raises(
+            mox.exceptions.ObjectResolutionError,
+            match="Could not resolve class, object or module from supplied reference",
+        ):
+            with mox.stubout("testtest_helpersmissing_packagefarawayFarAwayClassdistant_method") as mock_distant_method:
+                mock_distant_method().returns(True)
+
+
+class TestMockObjectStringImports:
+    """Verify that the MockObject class works as expected with string imports."""
+
+    def test_description_mocked_object(self, mock):
+        obj = FarAwayClass()
+
+        mock.stubout("test.test_helpers.subpackage.faraway.FarAwayClass.distant_method")
+        obj.distant_method().returns(True)
+
+        mock.replay_all()
+        assert obj.distant_method._description == "FarAwayClass.distant_method"
+        mock.reset_all()
+
+    def test_description_module_function(self, mock):
+        mock.stubout("test.mox_test_helper.MyTestFunction")
+        mox_test_helper.MyTestFunction(one=1, two=2).returns(True)
+
+        mock.replay_all()
+        assert mox_test_helper.MyTestFunction._description == "function test.mox_test_helper.MyTestFunction"
+        mock.reset_all()
+
+    def test_description_mocked_class(self, mock):
+        obj = FarAwayClass()
+
+        mock.stubout("test.test_helpers.subpackage.faraway.FarAwayClass.distant_method")
+        obj.distant_method().returns(True)
+
+        mock.replay_all()
+        assert obj.distant_method._description == "FarAwayClass.distant_method"
+        mock.reset_all()
+
+    def test_description_class_method(self, mock):
+        obj = mox_test_helper.SpecialClass()
+
+        mock.stubout("test.mox_test_helper.SpecialClass.class_method")
+        mox_test_helper.SpecialClass.class_method().returns(True)
+
+        mock.replay_all()
+        assert obj.class_method._description == "SpecialClass.class_method"
+        mock.unset_stubs()
+        mock.reset_all()
+
+    def test_description_static_method_mock_class(self, mock):
+        mock.stubout("test.mox_test_helper.SpecialClass.static_method")
+        mox_test_helper.SpecialClass.static_method().returns(True)
+
+        mock.replay_all()
+        assert mox_test_helper.SpecialClass.static_method._description in [
+            "SpecialClass.static_method",
+            "function test.mox_test_helper.static_method",
+        ]
+        mock.reset_all()
+
+    def test_description_static_method_mock_instance(self, mock):
+        obj = mox_test_helper.SpecialClass()
+
+        mock.stubout("test.mox_test_helper.SpecialClass.static_method")
+        obj.static_method().returns(True)
+
+        mock.replay_all()
+        assert obj.static_method._description in [
+            "SpecialClass.static_method",
+            "function test.mox_test_helper.static_method",
+        ]
+        mock.reset_all()
+
+
+class TestMockObjectContextManagerStringImports:
+    """Verify that the MockObject class works as expected with context managers and string imports."""
+
+    def test_description_mocked_object(self):
+        obj = FarAwayClass()
+
+        with mox.stubout("test.test_helpers.subpackage.faraway.FarAwayClass.distant_method") as stub, mox.expect:
+            obj.distant_method().returns(True)
+
+        assert obj.distant_method._description == "FarAwayClass.distant_method"
+
+        mox.reset(stub)
+
+    def test_description_module_function(self):
+        with mox.stubout("test.mox_test_helper.MyTestFunction") as stub, mox.expect:
+            mox_test_helper.MyTestFunction(one=1, two=2).returns(True)
+
+        assert mox_test_helper.MyTestFunction._description == "function test.mox_test_helper.MyTestFunction"
+
+        mox.reset(stub)
+
+    def test_description_mocked_class(self):
+        obj = FarAwayClass()
+
+        with mox.stubout("test.test_helpers.subpackage.faraway.FarAwayClass.distant_method") as stub, mox.expect:
+            obj.distant_method().returns(True)
+
+        assert obj.distant_method._description == "FarAwayClass.distant_method"
+
+        mox.reset(stub)
+
+    def test_description_class_method(self):
+        obj = mox_test_helper.SpecialClass()
+
+        with mox.stubout("test.mox_test_helper.SpecialClass.class_method") as stub, mox.expect:
+            mox_test_helper.SpecialClass.class_method().returns(True)
+
+        assert obj.class_method._description == "SpecialClass.class_method"
+
+        mox.reset(stub)
+
+    def test_description_static_method_mock_class(self):
+        with mox.stubout("test.mox_test_helper.SpecialClass.static_method") as stub, mox.expect:
+            mox_test_helper.SpecialClass.static_method().returns(True)
+
+        assert mox_test_helper.SpecialClass.static_method._description in [
+            "SpecialClass.static_method",
+            "function test.mox_test_helper.static_method",
+        ]
+
+        mox.reset(stub)
+
+    def test_description_static_method_mock_instance(self):
+        obj = mox_test_helper.SpecialClass()
+
+        with mox.stubout("test.mox_test_helper.SpecialClass.static_method") as stub, mox.expect:
+            obj.static_method().returns(True)
+
+        assert obj.static_method._description in [
+            "SpecialClass.static_method",
+            "function test.mox_test_helper.static_method",
+        ]
+
+        mox.reset(stub)
+
+
+class TestMoxStringImports:
+    """Verify Mox works correctly with string imports."""
+
+    def test_stub_out_method__unbound__subclass__comparator(self, mock):
+        mock_value = mock.stubout("test.mox_test_helper.TestClassFromAnotherModule.value")
+        mock_value(mox.is_a(mox_test_helper.ChildClassFromAnotherModule)).returns("foo")
+        mock.replay_all()
+
+        instance = mox_test_helper.ChildClassFromAnotherModule()
+        actual = mox_test_helper.TestClassFromAnotherModule.value(instance)
+
+        mock.verify_all()
+        mock.unset_stubs()
+        assert actual == "foo"
+
+    def test_stub_out__signature_matching_init_(self, mock):
+        mock_init = mock.stubout("test.mox_test_helper.ExampleClass.__init__")
+        mock_init(mox.IgnoreArg())
+
+        mock.replay_all()
+        # Create an instance of a child class, which calls the parent
+        # __init__
+        mox_test_helper.ChildExampleClass()
+        mock.verify_all()
+        mock.unset_stubs()
+
+    def test_stub_out_class(self, mock):
+        factory = mock.stubout_class("test.mox_test_helper.CallableClass")
+
+        # Instance one
+        mock_one = factory(1, 2)
+        mock_one.value().returns("mock")
+
+        # Instance two
+        mock_two = factory(8, 9)
+        mock_two("one").returns("called mock")
+
+        mock.replay_all()
+
+        one = mox_test_helper.CallableClass(1, 2)
+        actual_one = one.value()
+
+        two = mox_test_helper.CallableClass(8, 9)
+        actual_two = two("one")
+
+        mock.verify_all()
+        assert mox_test_helper.CallableClass == factory
+        mock.unset_stubs()
+
+        # Verify the correct mocks were returned
+        assert mock_one == one
+        assert mock_two == two
+
+        # Verify
+        assert "mock" == actual_one
+        assert "called mock" == actual_two
+
+    def test_stub_out_class_with_meta_class(self, mock):
+        factory = mock.stubout_class(mox_test_helper, "ChildClassWithMetaClass")
+
+        mock_one = mox_test_helper.ChildClassWithMetaClass(kw=1)
+        mock_one.value().returns("mock")
+
+        mock.replay_all()
+
+        one = mox_test_helper.ChildClassWithMetaClass(kw=1)
+        actual_one = one.value()
+
+        mock.verify_all()
+        assert mox_test_helper.ChildClassWithMetaClass == factory
+        mock.unset_stubs()
+
+        # Verify the correct mocks were returned
+        assert mock_one == one
+
+        # Verify
+        assert "mock" == actual_one
+        assert "meta" == one.x
+
+    def test_stub_out_class__a_b_c_meta(self, mock):
+        mock.stubout_class("test.mox_test_helper.CallableSubclassOfMyDictABC")
+
+        mock_foo = mox_test_helper.CallableSubclassOfMyDictABC(foo="!mock bar")
+        mock_foo["foo"].returns("mock bar")
+        mock_spam = mox_test_helper.CallableSubclassOfMyDictABC(spam="!mock eggs")
+        mock_spam("beans").returns("called mock")
+
+        mock.replay_all()
+
+        foo = mox_test_helper.CallableSubclassOfMyDictABC(foo="!mock bar")
+        actual_foo_bar = foo["foo"]
+
+        spam = mox_test_helper.CallableSubclassOfMyDictABC(spam="!mock eggs")
+        actual_spam = spam("beans")
+
+        mock.verify_all()
+        mock.unset_stubs()
+
+        # Verify the correct mocks were returned
+        assert mock_foo == foo
+        assert mock_spam == spam
+
+        # Verify
+        assert "mock bar" == actual_foo_bar
+        assert "called mock" == actual_spam
+
+    def test_stub_out_class__not_a_class(self, mock):
+        with pytest.raises(TypeError):
+            mock.stubout_class("test.mox_test_helper.MyTestFunction")
+
+    def test_stub_out_class_not_enough_created(self, mock):
+        mock.stubout_class("test.mox_test_helper.CallableClass")
+
+        mox_test_helper.CallableClass(1, 2)
+        mox_test_helper.CallableClass(8, 9)
+
+        mock.replay_all()
+        mox_test_helper.CallableClass(1, 2)
+
+        assert len(mock.stubs.cache) == 1
+        with pytest.raises(mox.ExpectedMockCreationError):
+            mock.verify_all()
+        assert len(mock.stubs.cache) == 0
+
+    def test_stub_out_class_wrong_signature(self, mock):
+        factory = mock.stubout_class("test.mox_test_helper.CallableClass")
+
+        with pytest.raises(AttributeError):
+            mox_test_helper.CallableClass()
+
+        assert mox_test_helper.CallableClass == factory
+        mock.unset_stubs()
+
+    def test_stub_out_class_wrong_parameters(self, mock):
+        factory = mock.stubout_class("test.mox_test_helper.CallableClass")
+
+        mox_test_helper.CallableClass(1, 2)
+
+        mock.replay_all()
+
+        with pytest.raises(mox.UnexpectedMethodCallError):
+            mox_test_helper.CallableClass(8, 9)
+        assert mox_test_helper.CallableClass == factory
+        mock.unset_stubs()
+
+    def test_stub_out_class_too_many_created(self, mock):
+        factory = mock.stubout_class("test.mox_test_helper.CallableClass")
+
+        mox_test_helper.CallableClass(1, 2)
+
+        mock.replay_all()
+        mox_test_helper.CallableClass(1, 2)
+        with pytest.raises(mox.UnexpectedMockCreationError):
+            mox_test_helper.CallableClass(8, 9)
+
+        assert mox_test_helper.CallableClass == factory
+        mock.unset_stubs()
+
+    def test_stub_out_first_class_method_verifies_signature(self, mock):
+        stub = mock.stubout("test.mox_test_helper.MyTestFunction")
+        assert mox_test_helper.MyTestFunction == stub
+
+        # Wrong number of arguments
+        with pytest.raises(AttributeError):
+            mox_test_helper.MyTestFunction(1)
+        mock.unset_stubs()
+
+    def test_method_signature_verification(self, mock):
+        options = [
+            ((), {}, True, False),
+            ((), {}, True, True),
+            ((1,), {}, True, False),
+            ((1,), {}, True, True),
+            ((), {"nine": 2}, True, False),
+            ((), {"nine": 2}, True, True),
+            ((1, 2), {}, False, False),
+            ((1, 2), {}, False, True),
+            ((1, 2, 3), {}, False, False),
+            ((1, 2, 3), {}, False, True),
+            ((1, 2), {"nine": 3}, False, False),
+            ((1, 2), {"nine": 3}, False, True),
+            ((1, 2, 3, 4), {}, True, False),
+            ((1, 2, 3, 4), {}, True, True),
+        ]
+
+        for args, kwargs, raises, stub_class in options:
+            if stub_class:
+                mock.stubout("test.mox_test_helper.ExampleClass.test_method")
+                obj = mox_test_helper.ExampleClass()
+            else:
+                obj = mox_test_helper.ExampleClass()
+                mock.stubout(obj, "test_method")
+
+            if raises:
+                with pytest.raises(AttributeError):
+                    obj.test_method(*args, **kwargs)
+            else:
+                obj.test_method(*args, **kwargs)
+            mock.unset_stubs()
+
+    def test_stub_out_re_works(self, mock):
+        stub = mock.stubout("re.search")
+
+        re.search("a", "ivan").returns("true")
+
+        mock.replay_all()
+        result = mox_test_helper.TestClass().re_search()
+        mock.verify_all()
+
+        assert re.search == stub
+        mock.unset_stubs()
+
+        assert result == "true"
+
+    def test_swallowed_unexpected_mock_creation(self, mock):
+        """Test that a swallowed UnexpectedMockCreationError will be re-raised."""
+        factory = mock.stubout_class("test.mox_test_helper.CallableClass")
+        mock.replay_all()
+
+        def call():
+            try:
+                mox_test_helper.CallableClass(1, 2)
+            except mox.UnexpectedMockCreationError:
+                pass
+
+        # UnexpectedMockCreationError swallowed
+        call()
+
+        assert mox_test_helper.CallableClass == factory
+        assert len(mock.stubs.cache) == 1
+        with pytest.raises(mox.SwallowedExceptionError):
+            mock.verify_all()
+        assert len(mock.stubs.cache) == 0
+
+
+class TestMoxContextManagerStringimports:
+    """Verify Mox works correctly when using context managers and string imports."""
+
+    def test_create_object_using_simple_imported_module_class_method(self):
+        """Mox should create a mock object for a class from a module imported
+        using a simple 'import module' statement"""
+
+        with mox.stubout("test.mox_test_helper.ExampleClass.class_method") as stub:
+            example_obj = mox.create(mox_test_helper.ExampleClass)
+            mox_test_helper.ExampleClass.class_method().returns(example_obj)
+
+        def call_helper_class_method():
+            return mox_test_helper.ExampleClass.class_method()
+
+        expected_obj = call_helper_class_method()
+        mox.verify(stub, example_obj)
+
+        assert expected_obj == example_obj
+
+    def test_stub_out_method__unbound__subclass__comparator(self):
+        with mox.stubout("test.mox_test_helper.TestClassFromAnotherModule.value") as stub:
+            ...
+
+        with stub._expect:
+            mox_test_helper.TestClassFromAnotherModule.value(
+                mox.is_a(mox_test_helper.ChildClassFromAnotherModule)
+            ).returns("foo")
+
+        instance = mox_test_helper.ChildClassFromAnotherModule()
+        actual = mox_test_helper.TestClassFromAnotherModule.value(instance)
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+        assert "foo" == actual
+
+    def test_stub_ou_method__unbound__with_optional_params(self):
+        with mox.stubout("test.mox_test_helper.TestClassFromAnotherModule.value") as stub:
+            ...
+
+        with stub._expect:
+            mox_test_helper.TestClass.optional_args(mox.IgnoreArg(), foo=2)
+
+        t = mox_test_helper.TestClass()
+        mox_test_helper.TestClass.optional_args(t, foo=2)
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_method__unbound__named_using_positional(self):
+        """Check positional parameters can be matched to keyword arguments."""
+        with mox.stubout("test.mox_test_helper.ExampleClass.named_params") as stub:
+            ...
+
+        instance = mox_test_helper.ExampleClass()
+        with stub._expect:
+            mox_test_helper.ExampleClass.named_params(instance, "foo", baz=None)
+
+        mox_test_helper.ExampleClass.named_params(instance, "foo", baz=None)
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_method__unbound__named_using_positional__some_positional(self):
+        """Check positional parameters can be matched to keyword arguments."""
+        with mox.stubout("test.mox_test_helper.ExampleClass.test_method") as stub:
+            ...
+
+        instance = mox_test_helper.ExampleClass()
+
+        with stub._expect:
+            mox_test_helper.ExampleClass.test_method(instance, "one", "two", "nine")
+
+        mox_test_helper.ExampleClass.test_method(instance, "one", "two", "nine")
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_method__unbound__special_args(self):
+        with mox.stubout("test.mox_test_helper.ExampleClass.special_args") as stub:
+            ...
+
+        instance = mox_test_helper.ExampleClass()
+
+        with stub._expect:
+            mox_test_helper.ExampleClass.special_args(instance, "foo", None, bar="bar")
+
+        mox_test_helper.ExampleClass.special_args(instance, "foo", None, bar="bar")
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_method__bound__simple_test(self):
+        t = mox.create(mox_test_helper.TestClass)
+        with t._expect:
+            t.method_with_args(mox.IgnoreArg(), mox.IgnoreArg()).returns("foo")
+
+        actual = t.method_with_args(None, None)
+
+        mox.verify(t)
+        m = mox.Mox._instances[t._mox_id]
+        m.unset_stubs()
+        assert "foo" == actual
+
+    def test_stub_out_method__bound__named_using_positional(self):
+        """Check positional parameters can be matched to keyword arguments."""
+        with mox.stubout("test.mox_test_helper.ExampleClass.named_params") as stub:
+            ...
+
+        instance = mox_test_helper.ExampleClass()
+        with stub._expect:
+            instance.named_params("foo", baz=None)
+
+        instance.named_params("foo", baz=None)
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_method__bound__named_using_positional__some_positional(self):
+        """Check positional parameters can be matched to keyword arguments."""
+        with mox.stubout("test.mox_test_helper.ExampleClass.test_method") as stub:
+            ...
+
+        instance = mox_test_helper.ExampleClass()
+        with stub._expect:
+            instance.test_method(instance, "one", "two", "nine")
+
+        instance.test_method(instance, "one", "two", "nine")
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_method__bound__special_args(self):
+        with mox.stubout("test.mox_test_helper.ExampleClass.special_args") as stub:
+            ...
+
+        instance = mox_test_helper.ExampleClass()
+
+        with stub._expect:
+            instance.special_args(instance, "foo", None, bar="bar")
+
+        instance.special_args(instance, "foo", None, bar="bar")
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out__signature_matching_init_(self):
+        with mox.stubout("test.mox_test_helper.ExampleClass.__init__") as stub:
+            ...
+
+        with stub._expect:
+            mox_test_helper.ExampleClass.__init__(mox.IgnoreArg())
+
+        # Create an instance of a child class, which calls the parent
+        # __init__
+        mox_test_helper.ChildExampleClass()
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_class__old_style(self):
+        """Test a mocked class whose __init__ returns a Mock."""
+        with mox.stubout("test.mox_test_helper.TestClassFromAnotherModule") as stub:
+            ...
+        assert isinstance(mox_test_helper.TestClassFromAnotherModule, mox.MockObject)
+
+        mock_instance = mox.create(mox_test_helper.TestClassFromAnotherModule)
+
+        with mox.expect(stub, mock_instance):
+            mox_test_helper.TestClassFromAnotherModule().returns(mock_instance)
+            mock_instance.value().returns("mock instance")
+
+        a_mock = mox_test_helper.TestClassFromAnotherModule()
+        actual = a_mock.value()
+
+        m = mox.Mox._instances[stub._mox_id]
+        m.verify_all()
+        m.unset_stubs()
+        mox.verify(mock_instance)
+        assert "mock instance" == actual
+
+    def test_stub_out_class(self):
+        with mox.stubout.klass("test.mox_test_helper.CallableClass") as stub:
+            # Instance one
+            mock_one = mox_test_helper.CallableClass(1, 2)
+            # Instance two
+            mock_two = mox_test_helper.CallableClass(8, 9)
+
+            mock_one.value().returns("mock")
+            mock_two("one").returns("called mock")
+
+        one = mox_test_helper.CallableClass(1, 2)
+        actual_one = one.value()
+
+        two = mox_test_helper.CallableClass(8, 9)
+        actual_two = two("one")
+
+        m = mox.Mox._instances[stub._mox_id]
+        m.verify_all()
+        m.unset_stubs()
+
+        # Verify the correct mocks were returned
+        assert mock_one == one
+        assert mock_two == two
+
+        # Verify
+        assert actual_one == "mock"
+        assert actual_two == "called mock"
+
+    def test_stub_out_class_with_meta_class(self):
+        with mox.stubout.klass("test.mox_test_helper.ChildClassWithMetaClass") as stub:
+            mock_one = mox_test_helper.ChildClassWithMetaClass(kw=1)
+            mock_one.value().returns("mock")
+
+        one = mox_test_helper.ChildClassWithMetaClass(kw=1)
+        actual_one = one.value()
+
+        m = mox.Mox._instances[stub._mox_id]
+        m.verify_all()
+        m.unset_stubs()
+
+        # Verify the correct mocks were returned
+        assert mock_one == one
+
+        # Verify
+        assert actual_one == "mock"
+        assert one.x == "meta"
+
+    def test_stub_out_class__a_b_c_meta(self):
+        with mox.stubout.klass("test.mox_test_helper.CallableSubclassOfMyDictABC") as stub:
+            mock_foo = mox_test_helper.CallableSubclassOfMyDictABC(foo="!mock bar")
+            mock_spam = mox_test_helper.CallableSubclassOfMyDictABC(spam="!mock eggs")
+            mock_foo["foo"].returns("mock bar")
+            mock_spam("beans").returns("called mock")
+
+        foo = mox_test_helper.CallableSubclassOfMyDictABC(foo="!mock bar")
+        actual_foo_bar = foo["foo"]
+
+        spam = mox_test_helper.CallableSubclassOfMyDictABC(spam="!mock eggs")
+        actual_spam = spam("beans")
+
+        m = mox.Mox._instances[stub._mox_id]
+        m.verify_all()
+        m.unset_stubs()
+
+        # Verify the correct mocks were returned
+        assert mock_foo == foo
+        assert mock_spam == spam
+
+        # Verify
+        assert "mock bar" == actual_foo_bar
+        assert "called mock" == actual_spam
+
+    def test_stub_out_class_not_enough_created(self):
+        with mox.stubout.klass("test.mox_test_helper.CallableClass") as stub:
+            ...
+
+        with stub._expect:
+            mox_test_helper.CallableClass(1, 2)
+            mox_test_helper.CallableClass(8, 9)
+
+        mox_test_helper.CallableClass(1, 2)
+
+        m = mox.Mox._instances[stub._mox_id]
+        len(m.stubs.cache) == 1
+        with pytest.raises(mox.ExpectedMockCreationError):
+            mox.verify(stub)
+        len(m.stubs.cache) == 0
+
+    def test_stub_out_class_wrong_signature(self):
+        with mox.stubout.klass("test.mox_test_helper.CallableClass") as stub:
+            with pytest.raises(AttributeError):
+                mox_test_helper.CallableClass()
+        # m = mox.Mox()
+        #
+        # m.stubout_class(mox_test_helper, "CallableClass")
+        # with pytest.raises(AttributeError):
+        #     mox_test_helper.CallableClass()
+
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_class_wrong_parameters(self):
+        with mox.stubout.klass("test.mox_test_helper.CallableClass") as stub:
+            ...
+
+        with stub._expect:
+            mox_test_helper.CallableClass(1, 2)
+
+        with pytest.raises(mox.UnexpectedMethodCallError):
+            mox_test_helper.CallableClass(8, 9)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_class_too_many_created(self):
+        with mox.stubout.klass("test.mox_test_helper.CallableClass") as stub:
+            ...
+
+        with stub._expect:
+            mox_test_helper.CallableClass(1, 2)
+
+        mox_test_helper.CallableClass(1, 2)
+        with pytest.raises(mox.UnexpectedMockCreationError):
+            mox_test_helper.CallableClass(8, 9)
+
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    # def test_warns_user_if_mocking_mock(self):
+    #     """Test that user is warned if they try to stub out a MockAnything."""
+    #     with mox.stubout("TestClass.my_static_method"):
+    #         with pytest.raises(TypeError):
+    #             with mox.stubout("TestClass.my_static_method"):
+    #                 ...
+
+    def test_stub_out_first_class_method_verifies_signature(self):
+        with mox.stubout("test.mox_test_helper.MyTestFunction") as stub:
+            # Wrong number of arguments
+            with pytest.raises(AttributeError):
+                mox_test_helper.MyTestFunction(1)
+        m = mox.Mox._instances[stub._mox_id]
+        m.verify_all()
+        m.unset_stubs()
+
+    @pytest.mark.parametrize(
+        "args,kwargs,raises,stub_class",
+        [
+            ((), {}, True, False),
+            ((), {}, True, True),
+            ((1,), {}, True, False),
+            ((1,), {}, True, True),
+            ((), {"nine": 2}, True, False),
+            ((), {"nine": 2}, True, True),
+            ((1, 2), {}, False, False),
+            ((1, 2), {}, False, True),
+            ((1, 2, 3), {}, False, False),
+            ((1, 2, 3), {}, False, True),
+            ((1, 2), {"nine": 3}, False, False),
+            ((1, 2), {"nine": 3}, False, True),
+            ((1, 2, 3, 4), {}, True, False),
+            ((1, 2, 3, 4), {}, True, True),
+        ],
+    )
+    def test_method_signature_verification(self, args, kwargs, raises, stub_class):
+        # If stub_class is true, the test is run against a stubbed out class,
+        # else the test is run against a stubbed out instance.
+        if stub_class:
+            with mox.stubout("test.mox_test_helper.ExampleClass.test_method") as stub:
+                obj = mox_test_helper.ExampleClass()
+        else:
+            obj = mox_test_helper.ExampleClass()
+            with mox.stubout(obj, "test_method") as stub:
+                ...
+
+        with stub._expect:
+            if raises:
+                with pytest.raises(AttributeError):
+                    obj.test_method(*args, **kwargs)
+            else:
+                obj.test_method(*args, **kwargs)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+    def test_stub_out_re_works(self):
+        with mox.stubout("re.search") as stub, mox.expect:
+            re.search("a", "ivan").returns("true")
+
+        result = mox_test_helper.TestClass().re_search()
+
+        mox.verify(stub)
+        m = mox.Mox._instances[stub._mox_id]
+        m.unset_stubs()
+
+        assert result == "true"
+
+    def test_swallowed_unexpected_mock_creation(self):
+        """Test that a swallowed UnexpectedMockCreationError will be re-raised."""
+        with mox.stubout.klass("test.mox_test_helper.CallableClass") as stub:
+            ...
+
+        def call():
+            try:
+                mox_test_helper.CallableClass(1, 2)
+            except mox.UnexpectedMockCreationError:
+                pass
+
+        # UnexpectedMockCreationError swallowed
+        call()
+
+        m = mox.Mox._instances[stub._mox_id]
+        len(m.stubs.cache) == 1
+        with pytest.raises(mox.SwallowedExceptionError):
+            mox.verify(stub)
+        len(m.stubs.cache) == 0