def register_processor2(processor_name='SomeProcessor'):
def decorator(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = processor_name
transaction_class = Transaction
@staticmethod
def setup(data=None):
pass
@wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
return decorator
def register_processor(func):
class SomeProcessor(GenericPaymentProcessor, TriggeredProcessorMixin):
name = 'SomeProcessor'
transaction_class = Transaction
@staticmethod
def setup(data=None):
pass
@wraps(func)
def func_wrapper(*args, **kwargs):
PaymentProcessorManager.register(SomeProcessor)
result = func(*args, **kwargs)
PaymentProcessorManager.unregister(SomeProcessor)
return result
return func_wrapper
class TestPaymentMethodEndpoints(APITestCase):
@register_processor
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
Ok so the decorator register_processor
works as expected. And the test fails, but I want to make the name of the inner class customizable so I went for a decorator factory implementation instead.
The thing is when running the test decorated with register_processor2
I get the following:
AttributeError: 'TestPaymentMethodEndpoints' object has no attribute '__name__'
This is from @wraps(func)
, my question is why is func
here an instance of TestPaymentMethodEndpoints
, and not the bound method?
Also if I remove the @wraps
decorator then the test runs and passes.
I'd expect that the test would not be discovered as func_wrapper
does not start with test_*
and even if it is discovered then it should fail.
Any insight on what is happening and how I'd go about doing this?
EDIT
So I figured it out even if the decorator factory has arguments that have default values you still need to place ()
when calling it.
But would still love to hear an explanation of what happened in case of the tests passing / getting discovered in the first place.
class TestPaymentMethodEndpoints(APITestCase):
@register_processor()
def test_put_detail_cannot_change_processor(self):
self.assertEqual(True, False)
Makes sense now that I think about it :D, gosh you learn something new each day!
I think you're now asking "how come the unittest
module can find test cases that have been wrapped in functions with names that don't start test
?"
The answer to that is because unittest
doesn't use the names of the functions to find the methods to run, it uses the attribute names of the test case classes to find them.
So try running the following code:
from unittest import TestCase
def apply_fixture(func):
def wrap_with_fixture(self):
print('setting up fixture...')
try:
func(self)
finally:
print('tearing down fixture')
return wrap_with_fixture
class MyTestCase(TestCase):
@apply_fixture
def test_something(self):
print('run test')
print('Attributes of MyTestCase: %s' % dir(MyTestCase))
print('test_something method: %s' % MyTestCase.test_something)
mtc = MyTestCase()
mtc.test_something()
You will see that the output from dir
contains the name test_something
:
Attributes of MyTestCase: ['__call__', ...lots of things..., 'test_something']
but that the value of that attribute is the wrapping function wrap_with_fixture
:
test_something method: <function apply_fixture.<locals>.wrap_with_fixture at 0x10d90aea0>
This makes sense when you consider that when you create a function you are both creating a function with the name provided and a local variable with the same name, and that the decorator @
syntax is just syntactic sugar, so the following would have been an equally valid albeit longer-winded way of creating your test case class:
class MyTestCase(TestCase):
def test_something(self):
print('run test')
# Overwrite existing 'local' (or 'class' variable in this context)
# with a new value. We haven't deleted the test_something function
# which still exists but now is owned by the function we've created.
test_something = apply_fixture(test_something)