4

So I have a huge object that holds information that is being initiated inside a fixture. I need to use this information to run my tests and here starts the tricky part. IF I do not have an attribute inside the object that is being used inside the test case I have to skip it.

The fixture with the generation of the object is being initiated once before test runs (in general). I need an easy-to-use decorator/fixture/whatever before the test that will check if the object has what it is needed inside the object.

Example:

@pytest.fixture(scope="package")
def info(request):
    print("Setting up...")
    obj = Creator()
    obj.setup()
    obj.prepare() if hasattr(obj, "prepare") else ""
    def teardown():
        obj.teardown() if hasattr(obj, "teardown") else ""
    request.addfinalizer(teardown)
    return obj.call()

...

@has_attr("some_attr")
def test_sometest(info):
    assert info.some_attr == 42
NFSpeedy
  • 161
  • 2
  • 10
  • Hey! I'm wondering if you're using one fixture for a variety of tests which could be partitioned into tests that use multiple, specifically tailored fixtures instead. Any thoughts? – oschlueter Oct 02 '20 at 17:35
  • The idea is that the fixture can be flexible and reused across tests. So in general when the tester writes a new test he can ensure that the info object has everything needed. There are cases where the info object misses an attr and this is normal. The testing is based on the values in the object and not if it has this and that. – NFSpeedy Oct 05 '20 at 08:28

1 Answers1

6

There are several possibilities I can think of to achieve this, none of which looks as clean as your example.

The easiest one is just to do the skipping inside the test:

def test_something(info):
    if not hasattr(info, "some_attr"):
        pytest.skip("Missing attribute 'some_attr'")
    assert info.some_attr == 42

Probably not what you want, but if you don't have many tests, it may make sense. If you have only a few different attributes you want to check, you can make specific fixtures for these attributes:

@pytest.fixture
def info_with_some_attr(info):
    if not hasattr(info, "some_attr"):
        pytest.skip("Missing attribute 'some_attr'")
    yield info

def test_something(info_with_some_attr):
    assert info_with_some_attr.some_attr == 42

If you have more attributes, you can parametrize the fixture with the attribute names instead:

@pytest.fixture
def info_with_attr(request, info):
    if hasattr(request, "param"):
        for attr in request.param:
            if not hasattr(info, attr):
                pytest.skip(f"Missing attribute '{attr}'")
    yield info


@pytest.mark.parametrize("info_with_attr", [("some_attr", "another_attr")], indirect=True)
def test_something(info_with_attr):
    assert info_with_attr.some_attr == 42

This does exactly what you want, although it looks a bit awkward.

Edit: Updated the last example to use a tuple instead of single string, as mentioned in the comments.

alon-k
  • 330
  • 2
  • 11
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
  • Thank you for your answer but it is not what I am looking for. I was searching for another approach - can we access variables inside the test in the fixture? In the beginning of the test we can sepcify a tuple with all the needed attributes and then check in the fixture if they are present. What do you think? – NFSpeedy Oct 05 '20 at 08:24
  • That is what the approach does - instead of passing a single attribute name, you could also pass a tuple (I will update the answer accordingly). But as I wrote, this looks a bit complicated - but this is the only way I know to pass test parameters back to the fixture (e.g. via the `request` fixture). – MrBean Bremen Oct 05 '20 at 08:53