pytest reuse fixture between projects - python

I want to create fixtures as library components.
A standard test database config is useful for several projects in different repos. It is currently copy/pasted into each independent project as they can't share a config.py.
I refactored the code into a pip installable library but can't work out an elegant way to use it in each project. This doesn't work:
import my_db_fixture
#pytest.fixture
def adapted_db_fixture(my_db_fixture):
# adapt the test setup
For the real code, the fixture I want to re-use is built from other fixtures. The best work-around I can find so far is to create a local conftest.py as copy/paste code but limited to importing functions and calling them in local fixture functions. I don't like copy/paste and unnecessarily exposes the inner workings of the fixtures.

It is possible to re-use fixtures from an installed library.
Define the fixtures as usual in the installable package. Then import them into a conftest.py local to the project. You need to import not just the fixture you want but also all fixtures it depends on and (if used) pytest_addoption
from my.package import (
the_fixture_i_want,
all_fixtures_it_uses,
pytest_addopt
)
I also discovered you can't un-decorate a library function with a teardown and call it in the local conftest.py:
# This doesn't work
# pip installed my_fixture.py
def my_fixture(dependencies)
# setup code
yield fixture_object
# teardown code
# local conftest.py
import pytest
import my_fixture # NB: the module
#pytest.fixture
def my_fixture(dependencies):
my_fixture.my_fixture()
# teardown code isn't called: pytest knows the function has no yield
# but doesn't realise it's returning a generator none the less
This article helped me:
peterhurford/pytest-fixture-modularization.md
I reckoned pytest should recognise something returning a generator as a generator so logged it as a bug. I imagine comments responding to it could be useful:
call_fixture_func should test the return value not the function

Related

Dynamically parametrizing test fixtures with addoption

I've got a test suite that is working well to test code across two separate databases (SQLite and Postgres). I want to extend this to run the exact same test suite across upgraded databases (to test that database schema upgrades are working as expected).
The upgrades to run are determined outside of pytest, from a shell script, based on information from Git, which determines what schema versions there are, compares against available upgrade scripts, and then should invoke pytest. I'd like to use something like:
pytest --dbupgrade=v1 --dbupgrade=v2 tests/test-upgrades.py
I have the following in conftest.py:
def pytest_addoption(parser):
parser.addoption(
"--dbupgrade",
action="append",
default=[],
help="list of base schema versions to upgrade"
)
And I've been using parametrized fixtures for the other tests. I already have all the test cases written and working and I would like to avoid rewriting them to be parametrized themselves as I've seen when searching for a solution using pytest_generate_tests. So where I could easily hardcode:
#pytest.fixture(params=['v1', 'v2'])
def myfixture(request):
...
I would like to do:
#pytest.fixture(params=pytest.config.option.get('dbupgrade')
def myfixture(request):
...
However the results from pytest_addoption are available in either the pytestconfig fixture, or in the config attribute attached to various objects, and I can't find a way to get it in the declaration of the fixture--but I believe it's available by that point.
Update (workaround)
I don't love it, but I'm pulling the necessary information from environment variables and that's working fine. Something like:
# for this case I prefer this to fail noisily if it fails
schema_versions = os.environ['SCHEMA_VERSIONS'].split(',')
...
#pytest.fixture(params=schema_versions)
def myfixture(request):
...

Chain multiple fixtures pytest/unittest hybrid

I am working with a code base which which is mostly written with unittest. The tests are run with pytest. I am trying to simplify the tests by parameterizing some of them. To work towards this goal, I am trying to learn how to use pytest fixtures and refactor some of the existing unittest-based tests. The current tests have a global fixture in conftest.py:
#pytest.fixture(autouse=True)
def register_cleanup():
yield True
print("cleaning up database")
Now I want to add a fixture which is specific to one of my test modules, something along the lines of
#pytest.fixture()
def foo_fixture():
print("setup fixture")
yield
print("tear down fixture")
class Foo(unittest.TestCase):
def setUp(self):
print('unittest setUp()')
def test(self):
print('test')
However, the print() statements in this module fixture never execute. As an intermediate step, I am keeping the unittest-based structure and adding to it. Is it possible to get this working the way I want or do I need to scrap unittest altogether and go directly to pytest.
You'll want to mark it:
#pytest.mark.usefixtures("foo_fixture")
class Foo(unittest.TestCase):
...
Note: you may have more than one conftest.py. Put the more specific fixture into this test subdirectory's conftest.py.
Where would you expect this fixture to be used?
Did you want to turn on autouse=True here as well? In general, fixtures are only executed when they are a parameter to the test, and are less of generic cleanup mechanisms than scoped assumptions. By turning on autouse for this fixture you will essentially add an extra setup-cleanup step for the current module:
https://docs.pytest.org/en/latest/fixture.html#autouse-fixtures-xunit-setup-on-steroids
Here is how autouse fixtures work in other scopes:
autouse fixtures obey the scope= keyword-argument: if an autouse
fixture has scope='session' it will only be run once, no matter where
it is defined. scope='class' means it will be run once per class, etc.
if an autouse fixture is defined in a test module, all its test
functions automatically use it. if an autouse fixture is defined in a
conftest.py file then all tests in all test modules below its
directory will invoke the fixture. lastly, and please use that with
care: if you define an autouse fixture in a plugin, it will be invoked
for all tests in all projects where the plugin is installed. This can
be useful if a fixture only anyway works in the presence of certain
settings e. g. in the ini-file. Such a global fixture should always
quickly determine if it should do any work and avoid otherwise
expensive imports or computation.
[Adaptied comment into an answer]

Can I override a fixture from a plugin in another plugin?

Lets assume I have:
pytest-3rd-party-plugin that provides a fixture cool_fixture
pytest-own-plugin that provides the fixture cool_fixture but with "better" functionality than cool_fixture
I want my own cool_fixture plugin to be the one that is selected. I know the following:
If I would have put that fixture in conftest.py, there wouldn't have been any problem, that would have been selected by the pytest runner. But I don't want to do that, as I am reusing this fixture in many projects.
I know that I can just call the fixture better_cool_fixture, and all will be fine :) But the fixture name is so pretty, so I kind of wanna keep it that way.
As I have understood pytest, there is no defined structure, what fixture that will be used. With exception of fixtures in module, conftest.py etc...
So anyway around this, so I don't need to change the name of this beautiful fixture?

In pytest, what is the use of conftest.py files?

I'm trying to understand what conftest.py files are meant to be used for.
In my (currently small) test suite I have one conftest.py file at the project root. I use it to define the fixtures that I inject into my tests.
I have two questions:
Is this the correct use of conftest.py? Does it have other uses?
Can I have more than one conftest.py file? When would I want to do that? Examples will be appreciated.
More generally, how would you define the purpose and correct use of conftest.py file(s) in a pytest test suite?
Is this the correct use of conftest.py?
Yes it is. Fixtures are a potential and common use of conftest.py. The
fixtures that you will define will be shared among all tests in your test suite. However, defining fixtures in the root conftest.py might be useless and it would slow down testing if such fixtures are not used by all tests.
Does it have other uses?
Yes it does.
Fixtures: Define fixtures for static data used by tests. This data can be accessed by all tests in the suite unless specified otherwise. This could be data as well as helpers of modules which will be passed to all tests.
External plugin loading: conftest.py is used to import external plugins or modules. By defining the following global variable, pytest will load the module and make it available for its test. Plugins are generally files defined in your project or other modules which might be needed in your tests. You can also load a set of predefined plugins as explained here.
pytest_plugins = "someapp.someplugin"
Hooks: You can specify hooks such as setup and teardown methods and much more to improve your tests. For a set of available hooks, read Hooks link. Example:
def pytest_runtest_setup(item):
""" called before ``pytest_runtest_call(item). """
#do some stuff`
Test root path: This is a bit of a hidden feature. By defining conftest.py in your root path, you will have pytest recognizing your application modules without specifying PYTHONPATH. In the background, py.test modifies your sys.path by including all submodules which are found from the root path.
Can I have more than one conftest.py file?
Yes you can and it is strongly recommended if your test structure is somewhat complex. conftest.py files have directory scope. Therefore, creating targeted fixtures and helpers is good practice.
When would I want to do that? Examples will be appreciated.
Several cases could fit:
Creating a set of tools or hooks for a particular group of tests.
root/mod/conftest.py
def pytest_runtest_setup(item):
print("I am mod")
#do some stuff
test root/mod2/test.py will NOT produce "I am mod"
Loading a set of fixtures for some tests but not for others.
root/mod/conftest.py
#pytest.fixture()
def fixture():
return "some stuff"
root/mod2/conftest.py
#pytest.fixture()
def fixture():
return "some other stuff"
root/mod2/test.py
def test(fixture):
print(fixture)
Will print "some other stuff".
Overriding hooks inherited from the root conftest.py.
root/mod/conftest.py
def pytest_runtest_setup(item):
print("I am mod")
#do some stuff
root/conftest.py
def pytest_runtest_setup(item):
print("I am root")
#do some stuff
By running any test inside root/mod, only "I am mod" is printed.
You can read more about conftest.py here.
EDIT:
What if I need plain-old helper functions to be called from a number
of tests in different modules - will they be available to me if I put
them in a conftest.py? Or should I simply put them in a helpers.py
module and import and use it in my test modules?
You can use conftest.py to define your helpers. However, you should follow common practice. Helpers can be used as fixtures at least in pytest. For example in my tests I have a mock redis helper which I inject into my tests this way.
root/helper/redis/redis.py
#pytest.fixture
def mock_redis():
return MockRedis()
root/tests/stuff/conftest.py
pytest_plugin="helper.redis.redis"
root/tests/stuff/test.py
def test(mock_redis):
print(mock_redis.get('stuff'))
This will be a test module that you can freely import in your tests. NOTE that you could potentially name redis.py as conftest.py if your module redis contains more tests. However, that practice is discouraged because of ambiguity.
If you want to use conftest.py, you can simply put that helper in your root conftest.py and inject it when needed.
root/tests/conftest.py
#pytest.fixture
def mock_redis():
return MockRedis()
root/tests/stuff/test.py
def test(mock_redis):
print(mock_redis.get(stuff))
Another thing you can do is to write an installable plugin. In that case your helper can be written anywhere but it needs to define an entry point to be installed in your and other potential test frameworks. See this.
If you don't want to use fixtures, you could of course define a simple helper and just use the plain old import wherever it is needed.
root/tests/helper/redis.py
class MockRedis():
# stuff
root/tests/stuff/test.py
from helper.redis import MockRedis
def test():
print(MockRedis().get(stuff))
However, here you might have problems with the path since the module is not in a child folder of the test. You should be able to overcome this (not tested) by adding an __init__.py to your helper
root/tests/helper/init.py
from .redis import MockRedis
Or simply adding the helper module to your PYTHONPATH.
In a wide meaning conftest.py is a local per-directory plugin. Here you define directory-specific hooks and fixtures. In my case a have a root directory containing project specific tests directories. Some common magic is stationed in 'root' conftest.py. Project specific - in their own ones. Can't see anything bad in storing fixtures in conftest.py unless they are not used widely (In that case I prefer to define them in test files directly)
I use the conftest.py file to define the fixtures that I inject into my tests, is this the correct use of conftest.py?
Yes, a fixture is usually used to get data ready for multiple tests.
Does it have other uses?
Yes, a fixture is a function that is run by pytest before, and sometimes
after, the actual test functions. The code in the fixture can do whatever you
want it to. For instance, a fixture can be used to get a data set for the tests to work on, or a fixture can also be used to get a system into a known state before running a test.
Can I have more than one conftest.py file? When would I want to do that?
First, it is possible to put fixtures into individual test files. However, to share fixtures among multiple test files, you need to use a conftest.py file somewhere centrally located for all of the tests. Fixtures can be shared by any test. They can be put in individual test files if you want the fixture to only be used by tests in that file.
Second, yes, you can have other conftest.py files in subdirectories of the top tests directory. If you do, fixtures defined in these lower-level conftest.py files will be available to tests in that directory and subdirectories.
Finally, putting fixtures in the conftest.py file at the test root will make them available in all test files.
Here are the official docs about using conftest.py to share fixtures:
conftest.py: sharing fixtures across multiple files
The conftest.py file serves as a means of providing fixtures for an entire directory. Fixtures defined in a conftest.py can be used by any test in that package without needing to import them (pytest will automatically discover them).
You can have multiple nested directories/packages containing your tests, and each directory can have its own conftest.py with its own fixtures, adding on to the ones provided by the conftest.py files in parent directories.

Python Testing - Reset all mocks?

When doing unit-testing with Python / PyTest, if you do you not have patch decorators or with patch blocks throughout your code, is there a way to reset all mocks at the end of every file / module to avoid inter-file test pollution?
It seems like something that is mocked in one Python test file remains mocked in other file with the same return value, which means my mocks are persisting between tests and files (when a patch decorator or with patch block is NOT used).
Is there any way around this other than patching? There wouldn't happen to be a mock.reset_all_mocks() or something like that, would there?
What I ended up doing was using the pytest-mock library. According to the Readme:
This plugin installs a mocker fixture which is a thin-wrapper around
the patching API provided by the excellent mock package, but with the
benefit of not having to worry about undoing patches at the end of a
test. (Emphasis added.)
So now I can do: mocker.patch.object(module, 'method', return_value='hi'), and the patch will be removed at the end of the test.
There is no need to use with any more so that this solution scales nicely if you have many mocks in one test or if you want to change mocks during the test.
After monkey-patching, I'm undoing it at the end of the test to avoid any leaking to other tests or limit the patching within the scope.
def test1(monkeypatch):
monkeypatch.setattr(...)
assert(...)
monkeypatch.undo()
why don't use monkeypatch ?
The monkeypatch function argument helps you to safely set/delete an attribute, dictionary item or environment variable or to modify sys.path for importing.
you can:
def test1(monkeypatch):
monkeypatch.setattr(.....

Categories