Search code examples
pythonpytestpython-decoratorspython-3.8

How can I wrap pytest.mark decorator in another decorator and maintain all properties?


Pytest has a decorator that allows you to pass metadata to tests. You can apply the decorator to a class, which will apply it to all tests within that class. You can also apply the decorator to individual tests, like so:

@pytest.mark.resources('a')
class FooTests:
   ...

   def test_stuff(self):
      pass

   @pytest.mark.resources('a','b','c')
   def test_something(self):
      pass

In this example, the resources metadata on the class level is applied to all tests, and the internal pytest.mark.resources overrides the class level declaration.

I am trying to write a decorator that maintains this behavior. So far this is what I have:

def set_resources(*args):
    def wrapper(func):
        @pytest.mark.resources(*args)
        def inner(*args, **kwargs):
            func(*args, **kwargs)
        return inner
    return wrapper

Now I can decorate tests like this:

@set_resources('a','b','c')
def test_foo(self):
   pass

And it works as expected. However attempting to apply my custom decorator at the class-level is not working. In fact, when I apply the custom decorator at the class level, PyTest discovery no longer discovers any of the tests in the class, leading me to believe that my decorator is somehow contaminating the entire process at some level.

Is there a way to modify my decorator to maintain the property that if applied to a class, it applies to all that class's tests? To re-iterate, my decorator does work at the individual test level, but breaks things at the class level.


Solution

  • I severely overcomplicated this.

    You can retain the behavior of the @pytest.mark.resources('a','b','c', ...) decorator by merely returning it in the function you're wrapping it with, like so:

    def set_resources(*args):
       return pytest.mark.resources(*args)
    

    This then works as expected:

    @set_resources('a','b','c')
    class SomeTests:
    
       @set_resources('a')
       def test_doing_stuff(self):
          assert True