In your tests, if you want to use mock.patch
in a with
statement, the mock library requires you that you use the return value of the patch as the mock object. Your test now become
@it.should('replace the original methods with the fake methods')
def test_should_replace_the_original_methods_with_the_fake_methods(case):
class FakeObject(object):
class Configuration(object):
spec = RealObject
def was_faked(self):
return True
with fake(FakeObject) as realObject:
fake_obj = realObject()
case.assertTrue(fake_obj.was_faked())
You can then use the following substitute or even get rid of it.
def substitute(obj, qualified_name, spec):
return mock.patch(qualified_name, new=obj, spec=spec)
Patching works by patching types at the calling site. The following excerpt from the documentation is important.
target should be a string in the form ‘package.module.ClassName’. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch from. The target is imported when the decorated function is executed, not at decoration time.
If you want to patch the actual type, without using the return value with the with
statement, you must not resolve the name of the class to a qualified name but local name.
The following changes
@it.should('replace the original methods with the fake methods')
def test_should_replace_the_original_methods_with_the_fake_methods(case):
...
with fake(FakeObject, '%s.%s' % (__name__,'RealObject')):
fake_obj = RealObject()
case.assertTrue(fake_obj.was_faked())
testdoubles__init__.py
def fake(obj, qualified_name=None):
"""
:rtype : mock._patch
:param obj:
"""
try:
configuration = obj.Configuration()
except AttributeError:
raise TypeError('A fake testdouble must have a Configuration class.')
try:
spec = configuration.spec
except AttributeError:
raise TestDoubleConfigurationError('The type to be faked was not specified.')
qualified_name = qualified_name or get_qualified_name(spec)
...
Now the problem is that you can't reliably find out where RealObject is coming from, at least I couldn't really find a way. You could assume that it is from the module where the calling function reside and do:
qualified_name = "%s.%s" % (obj.__module__, spec.__name__)