pytest-django add fixtures to live_server fixture - python

I need to add fixtures to the live_server fixture provided by pytest-django specifically an overwritten django_db_setup.
That being said I understand it is not ideal to run tests against a db that isn't flushed clean but it is what I am working with.
In our normal test suite we use overwrite the django_db_setup to do nothing in our conftest.py file as follows
#pytest.fixture(scope="session")
def django_db_setup():
pass
It appears that when I use the live_server fixture provided by pytest-django it does not honor this as it attempts to flush the db at the end of the tests. How would one go about circumventing this? I've found an end around shown below but I'd like to avoid it if there is a better solution.
#pytest.fixture(scope='session')
def my_live_server(request):
request.getfixturevalue('django_db_setup')
return live_server(request)

It appears that when I use the live_server fixture provided by pytest-django it does not honor this as it attempts to flush the db at the end of the tests.
You're absolutely right; using the live-server fixture in a test will silently trigger transactional behaviour (as if you would pass transactional_db fixture to the test). AFAIK this can't be turned off via configuration (I will be glad if proven wrong); one has to mess with pytest-django's internals. In your conftest.py:
# conftest.py
import pytest
#pytest.fixture(scope="session")
def django_db_setup():
pass
#pytest.fixture(autouse=True, scope='function')
def _live_server_helper(request):
if 'live_server' not in request.funcargnames:
return
request.getfixturevalue('django_db_setup')
live_server = request.getfixturevalue('live_server')
live_server._live_server_modified_settings.enable()
request.addfinalizer(live_server._live_server_modified_settings.disable)
Sure, it's not a nice solution, but it does the trick. You can at least "mitigate the possible damage" by introducing a custom marker so that the patched helper is only applied to marked tests:
#pytest.fixture(autouse=True, scope='function')
def _live_server_helper(request):
markers = [marker.name for marker in request.node.iter_markers()]
if 'live_server_no_flush' not in markers:
request.getfixturevalue('_live_server_helper')
return
# rest of code same as above
if 'live_server' not in request.funcargnames:
return
request.getfixturevalue('django_db_setup')
live_server = request.getfixturevalue('live_server')
live_server._live_server_modified_settings.enable()
request.addfinalizer(live_server._live_server_modified_settings.disable)
Now the new behaviour is applied only to tests marked with live_server_no_flush:
#pytest.mark.live_server_no_flush
def test_spam(live_server):
...

This is what I had to do to get around it. I am however getting a pytest warning for directly invoking the live_server fixture. This can be avoided pytest<4
#pytest.fixture(scope="session")
def my_live_server(request):
request.getfixturevalue("fixture_i_want")
return live_server(request)

Related

Run setup_class() class after class scope fixture defined in conftest

So, I have fixtures defined in conftest.py file with scope="class" as I want to run them before each test class is invoked. The conftest file is placed inside project root directory for it to be visible to every test module.
Now in one of the test modules, I have another setup function which I want to run once for that module only. But the problem is setup_class() method is called before running fixtures defined in conftest.py. Is this expected? I wanted it to be opposite because I want to use something done in the fixtures defined in conftest. How to do that?
Code -
conftest.py:
#pytest.fixture(scope="class")
def fixture1(request):
#set a
#pytest.fixture(scope="class")
def fixture1(request):
test_1.py:
#pytest.mark.usefixtures("fixture_1", "fixture_2")
class Test1():
#need this to run AFTER the fixture_1 & fixture_2
def setup_class():
#setup
#get a set in fixture_1
def test_1()
.....
I know that I could simply define a fixture in the test file instead of setup_class but then I will have to specify it in arguments of every test method in order it to be invoked by pytest. But suggestions are welcome!
I have exactly the same problem. Only now I have realized that the problem might be taht the setup_class is called before the fixture >-/
I think that this question is similar to this one
Pytest - How to pass an argument to setup_class?
And the problem is mixing the unittest and pytest methods.
I kind of did what they suggested - I ommitted the setup_class and created a new fixture within the particular test file,
calling the fixture in the conftest.py.
It works so far.
M.
The problem is that you can use the result of a fixture only in test function (or method) which is run by pytest. Here I can suggest a workaround. But of course I'm not sure if it suites your needs.
The workaround is to call the function from a test method:
conftest.py
#pytest.fixture(scope='class')
def fixture1():
yield 'MYTEXT'
test_1.py
class Test1:
def setup_class(self, some_var):
print(some_var)
def test_one(self, fixture1):
self.setup_class(fixture1)
Fixtures and setup_class are two different paradigms to initialize test functions (and classes). In this case, mixing the two creates a problem: The class-scoped fixtures run when the individual test functions (methods) run. On the other hand, setup_class runs before they do. Hence, it is not possible to access a fixture value (or fixture-modified state) from setup_class.
One of the solutions is to stop using setup_class entirely and stick with a fixtures-only solution which is the preferred way in pytest nowadays (see the note at the beginning).
# conftest.py or the test file:
#pytest.fixture(scope="class")
def fixture_1(request):
print('fixture_1')
# the test file:
class Test1():
#pytest.fixture(scope="class", autouse=True)
def setup(self, fixture_1, request):
print('Test1.setup')
def test_a(self):
print('Test1.test_a')
def test_b(self):
print('Test1.test_b')
Note that the setup fixture depends on fixture_1 and hence can access it.

I can have a generic fixture and call different values from that in my test?

I'm new on python and pytest, and at the momment I understand the basic of how work. But I can't find documentation to the below scenario:
I created a generic fixture that have the default mock values used in my project, and use this fixture in tests, it works fine. Something like:
#fixture
#pytest.fixture()
def generic_fixture(mocker):
mocker.patch('project.some_class.url', "http://some_url:5002")
#test
def test_1(generic_fixture):
do_something() # method that uses values from my generic fixture
assert True
Now I want to know if have a way to pass a parameter in my test to my fixture call, to make some decision, something like:
#fixture
#pytest.fixture()
def generic_fixture(mocker, parameter_url):
if parameter_url == None
mocker.patch('project.some_class.url', "http://some_url:5002")
else:
mocker.patch('project.some_class.url', parameter_url)
#test
def test_1(generic_fixture(parameter_url)):
do_something() # method that uses values from my generic fixture
assert True
How I can do It properly? I looked at monkeypath, #pytest.mark.parametrize and some other structures, but none of them look to do what I want.
You can use Pytest's marks for this.
#fixture
#pytest.fixture()
def generic_fixture(request, mocker):
parameter_url = request.node.get_closest_marker("generic_fixture_url")
...
#test
#pytest.mark.generic_fixture_url("url")
def test_1(generic_fixture):
...
Other options are to turn your fixture into a factory or to set some attribute on the module of your test.
The answer of D Malan help me a lot!
I'll put here my final solution, because I used what the above answer describe and other things, and I think it can be usefull for someone with similar structure, because at least for my was hard to figure it out (If is preferable, someone note me so I put this answer in the question):
conftest.py
def pytest_configure(config):
# to avoid warnings
config.addinivalue_line("markers", "my_fixture_url")
#pytest.fixture()
def my_fixture(request):
if request.node.get_closest_marker("my_fixture_url") and request.node.get_closest_marker("my_fixture_url").args[0]:
something = request.node.get_closest_marker("my_fixture_url").args[0]
else:
something = 'www.google.com'
...
test.py
#pytest.mark.my_fixture_url({'url': 'www.stackoverflow.com'})
def test_1(my_fixture):
...
def test_2(my_fixture):
...
I used a dictionary in my mark because I have situations where I want send more than one parameter, and/or use each one to override some default value.
Note I have a second test using the fixture but without marker. I can do it and use the default values from fixture.
This way I can use the same fixture in cases where I want the default values and in cases where I need to override part of the fixture configuration.
I hope it help someone :D

How to initialize the database with your test data for each module? Pytest-django

For each application in the project, you need to write tests. Also for each application you first need to upload your test data, which, after passing all the module tests, must be deleted.
I found several solutions, but none of them seems to me optimal
First:
in file conftest.py in each app I override method django_db_setup, but in this case, the data is not deleted after passing the tests in the module, and become available for other applications.
In theory, with the help of yield you can delete all the data after passing the tests.
#pytest.fixture(scope='module')
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
call_command('loaddata', './apps/accounts/fixtures/accounts.json')
call_command('loaddata', './apps/activation/fixtures/activation.json')
call_command('loaddata', './apps/questionnaire/fixtures/questionnaire.json')
yield
# delete test data
Second: in the class with tests write such a setup
#pytest.fixture(autouse=True)
def setup(self, db):
call_command('loaddata', './apps/accounts/fixtures/accounts.json')
call_command('loaddata', './apps/activation/fixtures/activation.json')
call_command('loaddata', './apps/questionnaire/fixtures/questionnaire.json')
In this case, the data will be loaded exactly as many times as there will be tests in the module, which also seems to be not quite correct.
I did something like this in my own tests :
from pytest_django.fixtures import _django_db_fixture_helper
#pytest.fixture(autoscope='module')
def setup_db(request, django_db_setup, django_db_blocker):
_django_db_fixture_helper(request,·django_db_blocker)
call_command('loaddata', 'path/to/fixture.json')
I think that pytest_django should export the _django_db_fixture_helper in its official API as a factory fixture.

Pytest fixture with scope "class" running on every method

I'm trying to create a test environment with Pytest. The idea is to group test methods into classes.
For every class/group, I want to attach a config fixture that is going to be parametrized. So that I can run all the tests with "configuration A" and then all tests with "configuration B" and so on.
But also, I want a reset fixture, that can be executed before specific methods or all methods of a class.
The problem I have there is, once I apply my reset fixture (to a method or to a whole class), the config fixture seems to work in the function scope instead of the class scope. So, once I apply the reset fixture, the config fixture is called before/after every method in the class.
The following piece of code reproduces the problem:
import pytest
from pytest import *
#fixture(scope='class')
def config(request):
print("\nconfiguring with %s" % request.param)
yield
print("\ncleaning up config")
#fixture(scope='function')
def reset():
print("\nreseting")
#mark.parametrize("config", ["config-A", "config-B"], indirect=True)
##mark.usefixtures("reset")
class TestMoreStuff(object):
def test_a(self, config):
pass
def test_b(self, config):
pass
def test_c(self, config):
pass
The test shows how the config fixture should work, being executed only once for the whole class. If you uncomment the usefixtures decoration, you can notice that the config fixture will be executed in every test method. Is it possible to use the reset fixture without triggering this behaviour?
As I mentioned in a comment, that seems to be a bug in Pytest 3.2.5.
There's a workaround, which is to "force" the scope of a parametrization. So, in this case if you include the scope="class" in the parametrize decorator, you get the desired behaviour.
import pytest
from pytest import *
#fixture(scope='class')
def config(request):
print("\nconfiguring with %s" % request.param)
yield
print("\ncleaning up config")
#fixture(scope='function')
def reset():
print("\nreseting")
#mark.parametrize("config", ["config-A", "config-B"], indirect=True, scope="class")
#mark.usefixtures("reset")
class TestMoreStuff(object):
def test_a(self, config):
pass
def test_b(self, config):
pass
def test_c(self, config):
pass
It depends on which version of pytest you are using.
There are some semantical problems to implement this in older versions of pytest. So, this idea is not yet implemented in older pytest. Someone has already given suggestion to implement the same. you can refer this
"Fixture scope doesn't work when parametrized tests use parametrized fixtures".
This was the bug.
You can refer this
This issue has been resolved in latest version of pytest. Here's the commit for the same with pytest 3.2.5
Hope it would help you.

Pytest call setup() before fixture

I am having difficulty using fixtures with my pytest unit tests.
I am using a test class like this:
class TestMyApp(object):
def setup(self):
self.client = mock_client()
#pytest.fixture
def client_item(self):
return self.client.create_item('test_item')
def test_something1(self, client_item):
# Test here.
pass
When I run the above test I get the following exception:
AttributeError: 'TestMyApp' object has no attribute 'client'
I believe this is because the client_item() fixture function is being called before the setup() function.
Am I using fixtures incorrectly? Or is there some way I can force setup() to be called before the fixture functions?
Thanks in advance.
Fixtures can use other fixtures, so you can use fixtures all the way down:
class TestMyApp(object):
#pytest.fixture
def client(self):
return mock_client()
#pytest.fixture
def client_item(self, client):
return client.create_item('test_item')
def test_something1(self, client_item):
# Test here.
pass
The documentation subtly recommends fixtures over xUnit style setup/teardown methods:
While these setup/teardown methods are simple and familiar to those coming from a unittest or nose background, you may also consider using pytest’s more powerful fixture mechanism which leverages the concept of dependency injection, allowing for a more modular and more scalable approach for managing test state, especially for larger projects and for functional testing.
It goes on to say that the two styles can be mixed, but isn't clear about the order in which things will happen.

Categories

Resources