Django Modular Testing - django

I have an "ok" test suite now, but I'm wanting to improve it. What happens is that I'm having to repeat setting up (limiting models for an example) users, property, school, and city objects.
Here is an example of something I have now, which works (note: could be broken because of changes made to simplify the example, but the logic is what I'm after):
class MainTestSetup(TestCase):
def setUp(self):
self.manage_text = 'Manage'
User = get_user_model()
# set up all types of users to be used
self.staff_user = User.objects.create_user('staff_user', 'staff#gmail.com', 'testpassword')
self.staff_user.is_staff = True
self.staff_user.save()
self.user = User.objects.create_user('user', 'user#gmail.com', 'testpassword')
self.city = City.objects.create(name="Test Town", state="TX")
self.school = School.objects.create(city=self.city, name="RE Test University",
long=-97.1234123, lat=45.7801234)
self.company = Company.objects.create(name="Test Company", default_school=self.school)
def login(self):
self.client.login(username=self.user.username,
password='testpassword')
def login_admin(self):
self.client.login(username=self.staff_user, password="testpassword")
class MainViewTests(MainTestSetup):
def test_home(self):
url = reverse('home-list')
manage_url = reverse('manage-property')
anon_response = self.client.get(url)
self.assertEqual(anon_response.status_code, 200)
self.assertNotContains(anon_response, self.manage_text)
self.login_admin()
admin_response = self.client.get(url)
self.assertContains(admin_response, self.manage_text)
def test_search(self):
url = reverse('search')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
...more tests
As you can see the MainViewTest inherits the setUp and login functions from the MainTestSetup class. This works ok, but I have many apps and not all need to set up all models. What I've tried to do is set up a set of mixins to include things like User, School, Company only in the TestSetups that I need.
This MainTestSetup would turn into something like:
class SchoolMixin(object):
def setUp(self):
self.city = City.objects.create(name="Test Town", state="TX")
self.school = School.objects.create(city=self.city, name="RE Test University",
long=-97.1234123, lat=45.7801234)
class CompanyMixin(SchoolMixin):
def setUp(self):
self.company = Company.objects.create(name="Test Company", default_school=self.school)
class UserMixin(object):
def setUp(self):
User = get_user_model()
# set up all types of users to be used
self.staff_user = User.objects.create_user('staff_user', 'staff#gmail.com', 'testpassword')
self.staff_user.is_staff = True
self.staff_user.save()
self.user = User.objects.create_user('user', 'user#gmail.com', 'testpassword')
def login(self):
self.client.login(username=self.user.username,
password='testpassword')
def login_admin(self):
self.client.login(username=self.staff_user, password="testpassword")
class MainTestSetup(UserMixin, CompanyMixin, TestCase):
def setUp(self):
self.manage_text = 'Manage'
This would allow a lot more flexibility for my test suite - this is only a small example. It would allow me in other apps to only include the Mixins that are necessary. For example if company was not needed, I would include just the SchoolMixin from the above example.
I believe my problem here is with inhertance of the setUp function. I'm not sure how to inherit correctly (through super, or though something else?). I've tried using super but haven't been able to get it to work. I have to admit, I'm not that great with classes/mixins yet, so any help/pointers would be much appreciated.

You can simplify and reduce the amount of code you have by using 2 libraries: WebTest and FactoryBoy. You won't need these Mixins.
https://pypi.python.org/pypi/django-webtest
https://github.com/rbarrois/factory_boy
Do the change step by step:
1. Starts with WebTest so you can get rid of your login_ method (and you won't need to prepare the passwords as well). With WebTest, you can specify the logged-in user when you load a page. For instance you will replace:
self.login_admin()
admin_response = self.client.get(url)
with:
admin_response = = self.app.get(url, user=self.admin)
2. Then use factory_boy to create all the objects you need. For instance you will replace:
self.staff_user = User.objects.create_user('staff_user', 'staff#gmail.com', 'testpassword')
self.staff_user.is_staff = True
self.staff_user.save()
with:
self.staff_user = StaffFactory.create()
3. Mix it up. Get rid of self.admin. Replace it with:
admin = AdminFactory.create()
response = = self.app.get(url, user=admin)
Once you've done all that, your code is going to be a lot shorter and easier to read. You won't need these mixins at all. For example your SchoolMixin can be replaced like this:
self.city = City.objects.create(name="Test Town", state="TX")
self.school = School.objects.create(city=self.city, name="RE Test University",
long=-97.1234123, lat=45.7801234)
replaced with:
school = SchoolFactory.create()
That's because factories can automatically create related entities with "SubFactories".
Here is a complete really simple example of a test using factories: http://codeku.co/testing-in-django-1

Related

Problem with testing custom admin actions

Running into some problems when testing my custom admin actions.
First I can show you an example of a test that works and the actions it's testing.
custom action, Product model
#admin.action(description="Merge selected products")
def merge_products(self, request, queryset):
list_of_products = queryset.order_by("created_at")
list_of_products = list(list_of_products)
canonical_product = list_of_products[0]
list_of_products.remove(canonical_product)
for p in list_of_products:
for ep in p.external_products.all():
ep.internal_product = canonical_product
ep.save()
p.save()
canonical_product.save()
related test
class MockRequest(object):
pass
class ProductAdminTest(TestCase):
def setUp(self):
self.product_admin = ProductAdmin(model=Product, admin_site=AdminSite())
User = get_user_model()
admin_user = User.objects.create_superuser(
username="superadmin", email="superadmin#email.com", password="testpass123"
)
self.client.force_login(admin_user)
def test_product_merge(self):
self.boot1 = baker.make("products.Product", title="Filippa K boots", id=uuid4())
self.boot2 = baker.make("products.Product", title="Filippa K boots", id=uuid4())
self.external_product1 = baker.make(
"external_products.ExternalProduct", internal_product=self.boot1
)
self.external_product2 = baker.make(
"external_products.ExternalProduct", internal_product=self.boot2
)
self.assertEqual(self.boot1.external_products.count(), 1)
self.assertEqual(self.boot2.external_products.count(), 1)
request = MockRequest()
queryset = Product.objects.filter(title="Filippa K boots")
self.product_admin.merge_products(request, queryset)
self.assertEqual(self.boot1.external_products.count(), 2)
self.assertEqual(self.boot2.external_products.count(), 0)
Might not be the pretties test but it works, so does the action.
The code above works as it should but has been running into problems when trying to test an almost identical action but for another model.
custom action, Brand model
#admin.action(description="Merge selected brands")
def merge_brands(self, request, queryset):
qs_of_brands = queryset.order_by("created_at")
list_of_brands = list(qs_of_brands)
canonical_brand = list_of_brands[0]
for brand in list_of_brands:
if brand.canonical:
canonical_brand = brand
list_of_brands.remove(canonical_brand)
for brand in list_of_brands:
brand.canonical_brand = canonical_brand
brand.save()
for product in Product.objects.filter(brand=brand):
product.brand = canonical_brand
product.save()
canonical_brand.save()
related test
class MockRequest(object):
pass
def setUp(self):
self.brand_admin = BrandAdmin(model=Brand, admin_site=AdminSite())
User = get_user_model()
admin_user = User.objects.create_superuser(
username="superadmin", email="superadmin#email.com", password="testpass123"
)
self.client.force_login(admin_user)
def test_brand_merge(self):
self.brand1 = baker.make("brands.Brand", title="Vans")
self.brand1.canonical_brand = self.brand1
self.brand1.active = True
self.brand1.save()
self.brand2 = baker.make("brands.Brand", title="Colmar")
self.boot1 = baker.make("products.Product", brand=self.brand1, id=uuid4())
self.boot2 = baker.make("products.Product", brand=self.brand2, id=uuid4())
self.assertEqual(self.boot1.brand, self.brand1)
self.assertEqual(self.boot2.brand, self.brand2)
self.assertEqual(self.brand1.active, True)
self.assertEqual(self.brand2.active, False)
request = MockRequest()
queryset = Brand.objects.all()
self.brand_admin.merge_brands(request, queryset)
self.assertEqual(self.boot1.brand, self.brand1)
self.assertEqual(self.boot2.brand, self.brand1)
self.assertEqual(self.brand1.active, True)
self.assertEqual(self.brand2.active, False)
self.assertEqual(self.brand1.canonical_brand, self.brand1)
self.assertEqual(self.brand2.canonical_brand, self.brand1)
It's not really necessary how the code above works but I included it for context.
The product admin action works both when I try it manually and in the test suite. The brand action does work as it should when tested manually but in the test suite, it does nothing.
self.brand_admin.merge_brands(request, queryset)
does not change the tested objects in any way.
I have another custom admin action for the Brand model and it's the same problem there, works like a charm when I try it manually but it does nothing in the test suite. Because of those facts, my guess is that the problem is related to my admin setup in the test suite but it's identical to the admin setup used for the first test which works.
Any ideas?
Gladly provide more context if needed.
It looks like you need to refresh the objects from the Database, in your test self.brand1, boot1, etc are in memory and will not be automatically updated from the database, you need to get the new values from the database.
https://docs.djangoproject.com/en/4.0/ref/models/instances/#refreshing-objects-from-database
If you need to reload a model’s values from the database, you can use the refresh_from_db() method. When this method is called without arguments the following is done:
All non-deferred fields of the model are updated to the values currently present in the database.
Any cached relations are cleared from the reloaded instance.
After you call your merge_brands you should refresh each object.
self.boot1.refresh_from_db()
self.boot2.refresh_from_db()
# etc..

Test cases development for filter query search in Django

I am testing a Django Library application, which has a Book model and a search bar to filter those books that checks title__icontains = 'q'.
The url pattern:
path('search_book/', views.BookSearchListView.as_view(), name='search_book'),
The url routing:
http://127.0.0.1:8000/catalog/search_book/?q=house
Implementation of the following Class-based view:
class BookSearchListView(BookListView):
paginate_by = 3
def get_queryset(self):
result = super(BookSearchListView, self).get_queryset()
query = self.request.GET.get('q')
if query:
query_list = query.split()
result = result.filter(
reduce(operator.and_,
(Q(title__icontains=q) for q in query_list))
)
return result
In my tests.py, I have to develop test cases for the above view, but do not understand how to go about it. I have attempted the following:
class BookSearchListViewTest(TestCase):
"""
Test case for the Book Search List View
"""
def setUp(self):
test_user1 = User.objects.create_user(username='testuser1', password='1X<ISRUkw+tuK')
test_user1.save()
test_author = Author.objects.create(first_name='John', last_name='Smith')
Book.objects.create(title='House', author=test_author, summary='Published in 1990',
isbn='123456789123')
Book.objects.create(title='Money', author=test_author, summary='Published in 1991',
isbn='9876543210123')
Book.objects.create(title='Mouse', author=test_author, summary='Published in 1992',
isbn='1293874657832')
def test_redirect_if_not_logged_in(self):
response = self.client.get(reverse('books'))
self.assertRedirects(response, '/catalog/customer_login/?next=/catalog/books/')
def test_query_search_filter(self):
self.assertQuerysetEqual(Book.objects.filter(title__icontains='House'), ["<Book: House>"])
While the test_query_search_filter test runs successfully, in my coverage report, the class BookSearchListView is not getting tested.
I am a complete novice in Django and have just started out with test cases.
If you have parameter in your url then you should send it via url in your test case.
You created a Book object which title is House in your setUp method so;
def test_query_filter(self):
# If you have login required to access 'books' then
# you have to login with 'client.login' first.
url = '{url}?{filter}={value}'.format(
url=reverse('books'),
filter='q', value='Hou')
# With string format finally we expect a url like;
# '/books/q=Hou'
self.client.login(username='testuser1', password='1X<ISRUkw+tu')
response = self.client.get(url)
...
# test cases
...
You can test it like this:
def test_redirect_if_not_logged_in(self):
self.client.login(username='testuser1', password='1X<ISRUkw+tu')
response = self.client.get(reverse('books'))
self.assertQuerysetEqual(response.context['object_list'], Book.objects.all(), transform= lambda x:x)
You can check the testing tools documentation for more details.

Django test Client can only login once?

Our team is currently writing tests for our application. I am currently writing code to acces the views. These views are behind a login-screen, so our test first have to login and than peform the rest of the test. I've run into a very strange error. Basically My tests can only login once.
As you can see in the example below, both classes are doing the exact same thing, yet only one of them succeeds with the login, the other gives a '302 doest not equal 200' assertion error.
If I comment out the bottom one, the one at the top works, and vice versa.
Code that is testing different views also doesnt work, unless I comment out all other tests.
It doesnt matter if I login like shown below, or use a different variant (like self.client.login(username='test', password='password')).
Me and my team have no idea why Django is behaving this way and what we are doing wrong. Its almost as if the connection remains open and we would have to add code to close it. But the django-documentation doesnt mention any of this. DOes anyone know what we are doing wrong?
class FunctieListView_tests(TestCase):
"""Function listview only shows the data for the current_user / tenant"""
def setUp(self):
self.tenant = get_tenant()
self.function = get_function(self.tenant)
self.client = Client(HTTP_HOST='tc.tc:8000')
self.user = get_user(self.tenant)
def test_correct_function_context(self):
# Test if the view is only displaying the correct context data
self.client.post(settings.LOGIN_URL, {
'username': self.user.username,
'password': 'password'
}, HTTP_HOST='tc.tc:8000')
response = self.client.get(reverse('functie_list'))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context['functie_templates'] != None)
self.assertEqual(response.context['functie_templates'][0],
FunctieTemplate.objects.filter(linked_tenant=self.tenant)[0])
class FunctieListView_2_tests(TestCase):
"""Role Listview only shows the data for the current_user / tenant"""
def setUp(self):
self.tenant = get_tenant()
self.function = get_function(self.tenant)
self.client = Client(HTTP_HOST='tc.tc:8000')
self.user = get_user(self.tenant)
def test_correct_function_context_second(self):
#login
# Test if the view is only displaying the correct context data
self.client.post(settings.LOGIN_URL, {
'username': self.user.username,
'password': 'password'
}, HTTP_HOST='tc.tc:8000')
response = self.client.get(reverse('functie_list'))
self.assertEqual(response.status_code, 200)
self.assertTrue(response.context['functie_templates'] != None)
self.assertEqual(response.context['functie_templates'][0],
FunctieTemplate.objects.filter(linked_tenant=self.tenant)[0])
The users, tenants and functions are defined in a seperate utils file like so:
def get_user(tenant, name='test'):
u = User.objects.create_user(name, '{}#test.test'.format(name), 'password')
u.save()
u.profile.tenant = tenant
u.profile.tenant_role = generis.models.TENANT_OWNER
u.profile.save()
return u
def get_function(tenant):
userfunction = UserFunction.objects.create(name='test_functie', linked_tenant=tenant)
userfunction.save()
return userfunction
def get_tenant(slug_var='tc'):
f = elearning.models.FontStyle(font='foobar')
f.save()
c = elearning.models.ColorScheme(name='foobar', title='foo', text='fleeb', background='juice', block_background='schleem', box='plumbus')
c.save()
t = elearning.models.Tenant(name='tc', slug=slug_var, default_font_style=f, default_color_scheme=c)
t.save()
return t
My guess is that it happens because you are instantiating the Client yourself in setUp. Although it looks fine the outcome is obviously different from the regular behavior. I never had problems with login using the preinitialized self.client of django.test.TestCase.
Looking at django.test.client.Client, it says in the inline documentation:
Client objects are stateful - they will retain cookie (and thus session) details for the lifetime of the Client instance.
and a still existing cookie would explain the behavior you describe.
I cannot find HTTP_HOST in django.test.client.py, so I'm not sure whether you are really using that Client class at all. If you need access to a live server instance during tests, you could use Django's LiveServerTestCase.

model_mommy - user to assignment relationship

I'm finally setting up testing for my Django app, but I'm having difficulties getting started. I'm using model_mommy to create dynamic data for my tests, but have the following problem:
The view I'm testing is supposed to show me all the assignments a particular user has to complete. To test this, I want to create 500 assignments, log into the app and check if they are shown. So far I have the following test cases:
class TestLogin(TestCase):
def setUp(self):
self.client = Client()
user = User.objects.create(username='sam')
user.set_password('samspassword')
user.save()
def test_login(self):
self.client.login(username='sam', password='samspassword')
response = self.client.get('/')
print (response.content)
self.assertEqual(response.status_code, 200)
and
class TestShowAssignments(TestCase):
def setUp(self):
user_recipe = Recipe(User, username='sam', password="samspassword")
self.assignment = Recipe(Assignment,
coders = related(user_recipe))
self.assignments = self.assignment.make(_quantity=500)
def test_assignments(self):
self.assertIsInstance(self.assignments[0],Assignment)
self.assertEqual(len(self.assignments),500)
The first test passes fine and does what it should: TestLogin logs the user in and shows his account page.
The trouble starts with TestShowAssignments, which creates 500 assignments but if I look at the assignments with print (self.assignments[0].coders), I get auth.User.None. So it doesn't add the user I defined as a relation to the assignments. What might be important here is that the coders field in the model is a m2m field, which I tried to address by using related, but that doesn't seem to work.
What also doesn't work is logging in: if I use the same code I use for logging in during TestLogin in TestShowAssignments, I can't log in and see the user page.
So, my question: How do I use model_mommy to create Assignments and add them to a specific user, so that I can log in as that user and see if the assignments are displayed properly?
Do you want 500 Assignments that all have User "sam" as a single entry in the 'coders' field? If so, try:
from model_mommy import mommy
...
class TestShowAssignments(TestCase):
def setUp(self):
self.user = mommy.make(User, username='sam', password='samspassword')
self.assignments = mommy.make(Assigment, coders=[self.user], _quantity=500)

Only lists and tuples may be used in a list field Validation Error

Hi I am implementing test cases for my models.
I am using Mongoengine0.9.0 + Django 1.8
My models.py
class Project(Document):
# commented waiting for org-group to get finalize
project_name = StringField()
org_group = ListField(ReferenceField(OrganizationGroup, required=False))
My Serializers.py
class ProjectSerializer(DocumentSerializer):
class Meta:
model = Project
depth = 1
test.py file
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
self.user = User.objects.create_user(
username='jacob', email='jacob#jacob.com', password='top_secret')
def test_post_put_project(self):
"""
Ensure we can create new clients in mongo database.
"""
org_group = str((test_utility.create_organization_group(self)).id)
url = '/project-management/project/'
data = {
"project_name": "googer",
"org_group": [org_group],
}
##import pdb; pdb.set_trace()
factory = APIRequestFactory()
user = User.objects.get(username='jacob')
view = views.ProjectList.as_view()
# Make an authenticated request to the view...
request = factory.post(url, data=data,)
force_authenticate(request, user=user)
response = view(request)
self.assertEqual(response.status_code, 200)
When I am running test cases I am getting this error
(Only lists and tuples may be used in a list field: ['org_group'])
The complete Stack Trace is
ValidationError: Got a ValidationError when calling Project.objects.create().
This may be because request data satisfies serializer validations but not Mongoengine`s.
You may need to check consistency between Project and ProjectSerializer.
If that is not the case, please open a ticket regarding this issue on https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues
Original exception was: ValidationError (Project:None) (Only lists and tuples may be used in a list field: ['org_group'])
Not getting why we cant pass object like this.
Same thing when I am posting as an request to same method It is working for me but test cases it is failing
The tests should be running using multipart/form-data, which means that they don't support lists or nested data.
You can override this with the format argument, which I'm guessing you probably want to set to json. Most likely your front-end is using JSON, or a parser which supports lists, which explains why you are not seeing this.