3

I have an AWS S3 directory containing several JSON files, which are used as test inputs.

I've created a PyTest module that downloads all JSON files once using a module wide fixture, and then runs several test functions - each being parameterized over the set of JSONs:

import pytest
import os
from tempfile import mkdtemp, TemporaryDirectory
from glob import glob

JSONS_AWS_PATH = 's3://some/path/'

def download_jsons():
    temp_dir = mkdtemp()
    aws_sync_dir(JSONS_AWS_PATH, temp_dir)
    json_filenames = glob(os.path.join(local_path, "*.json"))
    return json_filenames

@pytest.fixture(scope='module', params=download_jsons()) #<-- Invoking download_jsons() directly
def json_file(request):
    return request.param

def test_something(json_file):
   # Open the json file and test something

def test_something_else(json_file):
   # Open the json file and test something else

def test_another_thing(json_file):
   # you got the point...

This test module in itself works - the only pain point is how to cleanup the temp_dir at the end of the module\session.
Since download_jsons() is being invoked directly, before json_file fixture is even started - it has no context of its own. So I can't make it clean temp_dir after all the tests are done.

I would like to make download_jsons() a module\session scope fixture in itself. Something like:

fixture(scope='module')
def download_jsons():
   temp_dir = mkdtemp()
   # Download and as glob, as above
   yield json_filenames
   shutil.rmtree(temp_dir)

or

fixture(scope='module')
def download_jsons(tmpdir_factory):
    #...

as @Gabriela Melo has suggested.

The question is how to make the json_file fixture parameterized over the list returned by download_jsons(), without invoking it directly?

I've tried implementing this solution with either mark.parametrize, setup_module(), or pytest_generate_tests(metafunc) - but wasn't able to implement the exact functionality I was looking for.

Hodor
  • 41
  • 5
  • 1
    Is your question about using fixtures as parametrization arguments? This is not supported, see [issue #349](https://github.com/pytest-dev/pytest/issues/349). Or is it just about cleaning up a temp directory? This can be easily done in multiple ways, without needing to have the code in an extra fixture. – hoefling Feb 21 '20 at 10:34
  • Thanks! I've missed that issue. Good to know that's not supported. My question is how to create the temp dir before the `json_load()` fixture is evaluated the first time, so it could be parameterized over the tempdir's content. Then I need to delete the tempdir after the **last** invocation of the fixture (it's being used several times by several test functions). I couldn't find a way to do that. – Hodor Feb 23 '20 at 06:33
  • This could go even beyond a single `module` - I might need to download the JSONs once per session, parametrize tests from different modules over the contents of the tempdir, then delete the dir when the session ends. – Hodor Feb 23 '20 at 06:36

2 Answers2

1

If you want to use a resource for parametrization, it can't be returned by a fixture (at least with the current version of pytest). However, you can move the setup/teardown code out to the hooks - this will also enable parametrizing via pytest_generate_tests hook. Example: in the root dir of your project, create a file named conftest.py with the following contents:

from tempfile import TemporaryDirectory
from pathlib import Path


def pytest_sessionstart(session):
    # this is where we create the temp dir and download the files
    session.config._tempdir = TemporaryDirectory()
    d = session.config._tempdir.name
    aws_sync_dir(JSONS_BLOBBY_PATH, d)
    session.config._json_files = Path(d).glob("*.json")


def pytest_sessionfinish(session):
    # this is where we delete the created temp dir
    session.config._tempdir.cleanup()


def pytest_generate_tests(metafunc):
    if "file" in metafunc.fixturenames:
        # parametrize the file argument with the downloaded files
        files = metafunc.config._json_files
        metafunc.parametrize("file", files)

You can now use the file argument in tests as usual, e.g.

def test_ends_with_json(file):
    assert file.suffix == ".json"
hoefling
  • 59,418
  • 12
  • 147
  • 194
0

This seems to be what you're looking for: https://docs.pytest.org/en/latest/tmpdir.html#the-tmpdir-factory-fixture

(Using Pytest's tmpdir_factory fixture and setting the scope of your json_file function to session instead of module)

Gabriela Melo
  • 588
  • 1
  • 4
  • 15
  • I've tried that as well - and it doesn't seem to be the right solution. Since `json_file ` is being **parameterized** by `download_json()` - the code in `download_json()` runs before the fixture even starts. So I can't define the dir inside `json_file()` (even if it is session scope). Moreover, per [pytest documentation](https://docs.pytest.org/en/latest/fixture.html#scope-sharing-a-fixture-instance-across-tests-in-a-class-module-or-session) - if a fixture is being parametrized, it may run more than once, even if it is in `session` scope (I've added prints and verified that). – Hodor Feb 20 '20 at 21:55
  • In essence - I need `download_json` to be a fixture in itself, so it could take `tmpdir_factory` as input - but then I couldn't find a way to make it the parametrizing function of `json_file`. – Hodor Feb 20 '20 at 22:00
  • Thank you for your answer! It has helped me better phrase the question, which wasn't actually about `tempdir` creation but how to parametrize one fixture using another. I've edited the question accordingly. – Hodor Feb 20 '20 at 23:10