From 33544a0ba7516b1be7044dcfaeec044ad7c981e6 Mon Sep 17 00:00:00 2001 From: Tim Bell Date: Sat, 26 May 2018 14:59:10 +0200 Subject: [PATCH 1/5] created django_use_model marker for testing unmanaged models; cherry-picked from d466743 --- docs/helpers.rst | 26 ++++++++++++++++++ pytest_django/plugin.py | 46 ++++++++++++++++++++++++++++++++ pytest_django_test/app/models.py | 7 +++++ tests/test_database.py | 26 ++++++++++++++++-- 4 files changed, 103 insertions(+), 2 deletions(-) diff --git a/docs/helpers.rst b/docs/helpers.rst index 47e1ad76a..cce7c4bba 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -94,6 +94,32 @@ when trying to access the database. client('some-url-with-invalid-template-vars') +``pytest.mark.django_use_model`` - force model creation for unmanaged models +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. py:function:: pytest.mark.django_use_model(model) + + :type model: django model or list of django models + :param model: + Model or models to be created, should be used only with models that + have ``Meta.managed = False`` + + This will create requested model(s) for the scope of the marker. + Allows testing of unmanaged models that are normally not created. + + .. note:: + + To access database you still have to request access by using + ``pytest.mark.django_db`` + + Example usage:: + + @pytest.mark.django_db + @pytest.mark.django_use_model(Unmanaged) + def test_unmanaged(): + assert Unmanaged.objects.count() >= 0 + + Fixtures -------- diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index 1585c6983..dfb342b7c 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -184,6 +184,10 @@ def pytest_load_initial_conftests(early_config, parser, args): 'the `urls` attribute of Django `TestCase` objects. *modstr* is ' 'a string specifying the module of a URL config, e.g. ' '"my_app.test_urls".') + early_config.addinivalue_line( + 'markers', + 'django_use_model(model): force model creation, ' + 'even for unmanaged models. Model(s) are deleted at the end of scope') options = parser.parse_known_args(args) @@ -387,6 +391,48 @@ def _django_db_marker(request): getfixturevalue(request, 'db') +@pytest.fixture(autouse=True) +def _django_use_model(request): + """Implement ``django_use_model`` marker. + + Marker creates unmanaged models that normally aren't created. + Destroys it at the end of marked scope. + + Note that you still need to use ``django_db`` marker before this one. + The test unit should be decorated: + + @pytest.mark.django_db + @pytest.mark.django_use_model(model) + + :model: ModelClass, one or many + """ + marker = request.keywords.get('django_use_model', None) + if not marker: + return + from django.db import connection + + model = request.getfuncargvalue('model') + + if isinstance(model, (list, tuple)): + models = model + else: + models = (model,) + + with contextlib.closing(connection.schema_editor()) as schema: + schema.deferred_sql = [] + for model_class in models: + if not hasattr(model, '_meta'): + raise ValueError('"model" must be a valid model class') + schema.create_model(model_class) + + def drop(): + with contextlib.closing(connection.schema_editor()) as schema: + for model_class in models: + schema.delete_model(model_class) + + request.addfinalizer(drop) + + @pytest.fixture(autouse=True, scope='class') def _django_setup_unittest(request, django_db_blocker): """Setup a django unittest, internal to pytest-django.""" diff --git a/pytest_django_test/app/models.py b/pytest_django_test/app/models.py index 717c02048..86c1889ee 100644 --- a/pytest_django_test/app/models.py +++ b/pytest_django_test/app/models.py @@ -9,3 +9,10 @@ def __unicode__(self): def __str__(self): return self.name + + +class Unmanaged(models.Model): + name = models.CharField(max_length=100) + + class Meta: + managed = False diff --git a/tests/test_database.py b/tests/test_database.py index ee3912c76..0a855132d 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,11 +1,11 @@ from __future__ import with_statement import pytest -from django.db import connection +from django.db import connection, DatabaseError from django.test.testcases import connections_support_transactions from pytest_django.pytest_compat import getfixturevalue -from pytest_django_test.app.models import Item +from pytest_django_test.app.models import Item, Unmanaged def test_noaccess(): @@ -141,6 +141,28 @@ def test_transactions_enabled(self): assert not connection.in_atomic_block +@pytest.mark.django_db +class TestUseModel: + """Tests for django_use_model marker""" + + def test_unmanaged_missing(self): + """Test that Unmanaged model is not created by default""" + with pytest.raises(DatabaseError): + # If table does not exists, django will raise DatabaseError + # but the message will depend on the backend. + # Probably nothing else can be asserted here. + Unmanaged.objects.exists() + + @pytest.mark.django_use_model(Unmanaged) + def test_unmanaged_created(self): + """Make sure unmanaged models are created""" + assert Unmanaged.objects.count() == 0 + + def test_unmanaged_destroyed(self): + """Test that Unmanaged model was destroyed after last use""" + self.test_unmanaged_missing() + + def test_unittest_interaction(django_testdir): "Test that (non-Django) unittests cannot access the DB." From 85f96ac1fd4abab0fe9f7402ad2af793cdc33711 Mon Sep 17 00:00:00 2001 From: Jakub Owczarski Date: Fri, 25 Sep 2015 08:47:52 +0000 Subject: [PATCH 2/5] tests marked with django_use_model not executed --- tests/test_database.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_database.py b/tests/test_database.py index 0a855132d..b71e696ff 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -163,6 +163,12 @@ def test_unmanaged_destroyed(self): self.test_unmanaged_missing() + +@pytest.mark.django_use_model(Unmanaged) +def test_marked_test_not_get_hit(): + assert True is False + + def test_unittest_interaction(django_testdir): "Test that (non-Django) unittests cannot access the DB." From 7466c900d2814de1c005d5e94fe7364685d4003a Mon Sep 17 00:00:00 2001 From: Tim Bell Date: Fri, 29 Jan 2016 11:11:57 +1100 Subject: [PATCH 3/5] Rework django_use_model to fix issues noted in #270 --- docs/helpers.rst | 6 +++--- pytest_django/plugin.py | 8 ++++---- tests/test_database.py | 20 ++++++++++++++++---- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/docs/helpers.rst b/docs/helpers.rst index cce7c4bba..ad062fe5a 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -97,9 +97,9 @@ when trying to access the database. ``pytest.mark.django_use_model`` - force model creation for unmanaged models ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. py:function:: pytest.mark.django_use_model(model) +.. py:function:: pytest.mark.django_use_model(model=ModelClass) - :type model: django model or list of django models + :type model: django model or list of django models, as kwarg :param model: Model or models to be created, should be used only with models that have ``Meta.managed = False`` @@ -115,7 +115,7 @@ when trying to access the database. Example usage:: @pytest.mark.django_db - @pytest.mark.django_use_model(Unmanaged) + @pytest.mark.django_use_model(model=Unmanaged) def test_unmanaged(): assert Unmanaged.objects.count() >= 0 diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index dfb342b7c..f7aa87592 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -402,7 +402,7 @@ def _django_use_model(request): The test unit should be decorated: @pytest.mark.django_db - @pytest.mark.django_use_model(model) + @pytest.mark.django_use_model(model=ModelClass) :model: ModelClass, one or many """ @@ -411,14 +411,14 @@ def _django_use_model(request): return from django.db import connection - model = request.getfuncargvalue('model') + model = marker.kwargs['model'] if isinstance(model, (list, tuple)): models = model else: models = (model,) - with contextlib.closing(connection.schema_editor()) as schema: + with connection.schema_editor() as schema: schema.deferred_sql = [] for model_class in models: if not hasattr(model, '_meta'): @@ -426,7 +426,7 @@ def _django_use_model(request): schema.create_model(model_class) def drop(): - with contextlib.closing(connection.schema_editor()) as schema: + with connection.schema_editor() as schema: for model_class in models: schema.delete_model(model_class) diff --git a/tests/test_database.py b/tests/test_database.py index b71e696ff..922e40cfa 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -141,6 +141,8 @@ def test_transactions_enabled(self): assert not connection.in_atomic_block +@pytest.mark.skipif(not hasattr(connection, 'schema_editor'), + reason="This Django version does not support SchemaEditor") @pytest.mark.django_db class TestUseModel: """Tests for django_use_model marker""" @@ -153,7 +155,7 @@ def test_unmanaged_missing(self): # Probably nothing else can be asserted here. Unmanaged.objects.exists() - @pytest.mark.django_use_model(Unmanaged) + @pytest.mark.django_use_model(model=Unmanaged) def test_unmanaged_created(self): """Make sure unmanaged models are created""" assert Unmanaged.objects.count() == 0 @@ -163,10 +165,20 @@ def test_unmanaged_destroyed(self): self.test_unmanaged_missing() - -@pytest.mark.django_use_model(Unmanaged) +# TODO: Remove this next test before release +@pytest.mark.skipif(not hasattr(connection, 'schema_editor'), + reason="This Django version does not support SchemaEditor") +@pytest.mark.django_db +@pytest.mark.django_use_model(model=Unmanaged) def test_marked_test_not_get_hit(): - assert True is False + """ + A failing test like this was originally added to demonstrate that adding + "@pytest.mark.django_use_model(ModelClass)" caused a test not to be + collected. It's left here to demonstrate that it's now being collected + when called with model=ModelClass instead; it should be removed before + the next release. + """ + assert "This test failing shows that it is being collected and run" is False def test_unittest_interaction(django_testdir): From b047a3f5dea90545552915d15243bcfcb9d3f440 Mon Sep 17 00:00:00 2001 From: Tim Bell Date: Fri, 29 Jan 2016 11:18:42 +1100 Subject: [PATCH 4/5] Update django_use_model mark registration --- pytest_django/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytest_django/plugin.py b/pytest_django/plugin.py index f7aa87592..fa4069f8b 100644 --- a/pytest_django/plugin.py +++ b/pytest_django/plugin.py @@ -186,7 +186,7 @@ def pytest_load_initial_conftests(early_config, parser, args): '"my_app.test_urls".') early_config.addinivalue_line( 'markers', - 'django_use_model(model): force model creation, ' + 'django_use_model(model=ModelClass): force model creation, ' 'even for unmanaged models. Model(s) are deleted at the end of scope') options = parser.parse_known_args(args) From 8bd58c1270511622b5df353b9c179fb93eeae4ab Mon Sep 17 00:00:00 2001 From: Tim Bell Date: Fri, 29 Jan 2016 11:37:57 +1100 Subject: [PATCH 5/5] Remove deliberately failing django_use_model test --- tests/test_database.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/test_database.py b/tests/test_database.py index 922e40cfa..3b22e60a6 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -165,22 +165,6 @@ def test_unmanaged_destroyed(self): self.test_unmanaged_missing() -# TODO: Remove this next test before release -@pytest.mark.skipif(not hasattr(connection, 'schema_editor'), - reason="This Django version does not support SchemaEditor") -@pytest.mark.django_db -@pytest.mark.django_use_model(model=Unmanaged) -def test_marked_test_not_get_hit(): - """ - A failing test like this was originally added to demonstrate that adding - "@pytest.mark.django_use_model(ModelClass)" caused a test not to be - collected. It's left here to demonstrate that it's now being collected - when called with model=ModelClass instead; it should be removed before - the next release. - """ - assert "This test failing shows that it is being collected and run" is False - - def test_unittest_interaction(django_testdir): "Test that (non-Django) unittests cannot access the DB."