question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Function-scoped fixture run before session-scoped with unittest.TestCase

See original GitHub issue

Currently unittest.TestCase instances cannot use fixtures in their test specifications, reportedly due to “different design philosophies”.

Would it be possible to create a class decorator to enable this? See this example (runs both with python -m unittest and pytest):

import unittest
import pytest

def fixture_wrapper(fn, params):
    # XXX discover fixture instead of hardcoding it here
    # pytest seems to add extra @py_assert1@ and @py_format1@ stuff here.
    #assert params == ('program',)
    assert params[0] == 'program'
    return lambda self, *args: fn(self, program())

def uses_fixtures(cls):
    '''Wraps all unittest.TestCase methods in order to inject fixtures.'''
    assert issubclass(cls, unittest.TestCase)
    for name in dir(cls):
        func = getattr(cls, name)
        if not name.startswith('test') or not callable(func):
            continue
        if func.__code__.co_argcount <= 1:
            # ignore methods which do not have fixture dependencies
            continue
        params = func.__code__.co_varnames[1:]
        print(name, params)
        setattr(cls, name, fixture_wrapper(func, params))

@pytest.fixture
def program():
    return 'dummy'

class TestDemo(object):
    def test_1(self, program):
        assert program == 'dummy'

@uses_fixtures
class DemoTestCase(unittest.TestCase):
    def test_2(self, program):
        assert program == 'dummy'

The motivation is to provide a transition path for an existing unittest suite to pytest. Currently I use pytest as test runner for an existing project that otherwise does not depend on pytest. To fix some issues (inability to run part of the tests when some program dependencies are missing) I would like to use fixtures, but without adding a hard dependency on pytest for now. With such a decorator, migration to pytest will require less extensive changes (dropping a decorator vs changing every test function to use the parameter instead of a property that was configured via the unittest setUp method).

Edit: one motivation for using fixtures is the ability to skip tests from it. E.g. if “program” is not available, skip the test.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:10 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
nicoddemuscommented, Oct 14, 2018

Hi @Lekensteyn,

I wholeheartedly understand the desire of using fixtures in unittest.TestCase subclasses as a stop gap measure when working with existing unittest-based test suites, I’ve done this myself countless times.

One approach that works out of the box is using autouse fixtures to inject other fixtures in the class:

class DemoTestCase(unittest.TestCase):

    @pytest.fixture(autouse=True)
    def _inject_fixtures(self, program):
        self.program = program

    def test_2(self):
        assert self.program == 'dummy'

From that, it follows you can then use the same technique to obtain access to request and inject the fixtures into the methods:

import functools
import unittest
import pytest
import inspect

def fixture_wrapper(fn, params):
    params = params[1:]  # skip self

    @functools.wraps(fn)
    def wrapped(self):
        fixtures = (self.request.getfixturevalue(n) for n in params)
        fn(self, *fixtures)

    return wrapped

def uses_fixtures(cls):
    '''Wraps all unittest.TestCase methods in order to inject fixtures.'''
    assert issubclass(cls, unittest.TestCase)
    
    for name in dir(cls):
        func = getattr(cls, name)
        if not name.startswith('test') or not callable(func):
            continue
        argspec = inspect.getfullargspec(func)
        if len(argspec.args) <= 1:
            # ignore methods which do not have fixture dependencies
            continue
        setattr(cls, name, fixture_wrapper(func, argspec.args))

    @pytest.fixture(autouse=True)
    def __inject_request(self, request):
        self.request = request
    cls.__inject_request = __inject_request
    return cls

@pytest.fixture
def program():
    return 'dummy'

class TestDemo(object):
    def test_1(self, program):
        assert program == 'dummy'

@uses_fixtures
class DemoTestCase(unittest.TestCase):
    def test_2(self, program):
        assert program == 'dummy'

This is an interesting idea, and it can be made automatic using a conftest.py file or plugin:

@pytest.hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj):
    from _pytest.unittest import UnitTestCase
    outcome = yield
    item = outcome.get_result()
    if isinstance(item, UnitTestCase):
        uses_fixtures(item.obj)

So the user doesn’t even need the uses_fixture decorator:

class DemoTestCase(unittest.TestCase):
    def test_2(self, program):
        assert program == 'dummy'

I’m not sure how far we want to take it by adding this to the core, as the possibility of maintenance problems is very real, but this could be made into an external plugin very easily (we can even mention it in the official pytest docs).

0reactions
nicoddemuscommented, Mar 1, 2019
Read more comments on GitHub >

github_iconTop Results From Across the Web

function-scoped fixture run before session-scoped fixture #5303
I have a case where a conftest in a subdirectory overrides a fixture with the same name from the enclosing directory's conftest, and...
Read more >
ScopeMismatch on using session scoped fixture with pytest ...
I am using pytest-mozwebqa plugin for running some selenium tests. I want to login to the application ...
Read more >
pytest fixtures: explicit, modular, scalable
pytest minimizes the number of active fixtures during test runs. If you have a parametrized fixture, then all the tests using it will...
Read more >
A session-fixture which can look at all collected tests - Pytest
A session-scoped fixture effectively has access to all collected test items. Here is an example of a fixture function which walks all collected...
Read more >
pytest session scoped fixtures | pythontest - Brian Okken
In pytest fixtures nuts and bolts, I noted that you can specify session scope so that a fixture will only run once per...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found