Django unit test for function - django

I need to test if a user is active. I have that function:
Service.py
#validate_input({
'code': {'required': True}
})
def signup_complete(self, data):
try:
code = VerificationCode.objects.select_related('user').get(code=data["code"],
code_type="registration",
expiration_date__gt=timezone.now())
except VerificationCode.DoesNotExist:
raise NotFound(_(u"Неверный код восстановленыя"), 1001)
user = code.user
user.is_active = True
user.save()
code.delete()
and I try to write a test for this function, but I don't know what argument I need to send.
Test.py
def test_signup_complete(self):
user = SiteUser.objects.get(email="Test#gmail.com")
code = VerificationCode.objects.get_or_create(user=user, code_type="registration", code=user.code)
UserService(user).signup_complete()
self.assertEqual(user.is_active, True)

You can reproduce all the steps needed for user registration and activation. Accessing registration page, sending filled form, checking emails with activation code etc. With this approach (which is much more reliable in comparison to your current code) you can get the confirmation code in the same way as real user gets it (for example, from profile activation email).

Related

How to check with pytest if one function was called inside Django form save method?

What i Have
Within the save method of the form are called two functions: generate_random_password and send_email, I need to know if these functions were called because generate_random_password assigns a password randomly to each new created user and send_email sends a email notification to the user with the credentials, the password generated and the user to login. It is important to know if these functions were executed correctly within save.
class UserAdminCreationForm(forms.ModelForm):
class Meta:
model = User
fields = ()
def save(self, commit: bool = True) -> U:
user = super(UserAdminCreationForm, self).save(commit=False)
data = self.cleaned_data
# Set random password for every new user
random_password = generate_random_password()
user.set_password(random_password)
# Send email confirmation with credentials to login
email = data.get("email")
html_message = render_to_string(
template_name="mails/user_creation_notification.html",
context={"email": email, "password": random_password},
)
# Strip the html tag. So people can see the pure text at least.
plain_message = strip_tags(html_message)
send_mail(
subject="Bienvenido a TodoTránsito",
message=plain_message,
recipient_list=[email],
html_message=html_message,
)
# Save into the DB the new user
if commit:
user.save()
return user
The problem
I'm using pytest to test my Django code, but I don't know how to assert if these functions were called.
From what you wrote in the comments, I would guess mocking both functions could make sense, as you probably don't really need the random password in your test. You could do something like:
from unittest import mock
#mock.patch('some_module.generate_random_password', return_value='swordfish')
#mock.patch('some_module.send_mail')
def test_save_user(mocked_send_mail, mocked_generate_password):
user_form = create_user_form() # whatever you do to create the form in the test
user_from.save()
mocked_generate_password.assert_called_once()
mocked_send_mail.assert_called_once()
# or mocked_send_mail.assert_called_once_with(...) if you want to check the parameters it was called with
Note that you have to make sure to mock the correct module, e.g. the one used in the tested code (see where to patch).
In this case generate_random_password is replaced with a mock that always returns the same password, and send_mail is replaced by a mock that does nothing except recording calls. Both mocks can be accessed via the arguments in the test, that are injected by the patch decorators (last decorator first, the argument names are arbitrary).
If you install pytest-mock, you get the mocker fixture, that gives you the same functionality and more. The same code would like like this:
def test_save_user(mocker):
mocked_generate_password = mocker.patch('some_module.generate_random_password', return_value='swordfish')
mocked_send_mail = mocker.patch('some_module.send_mail')
user_form = create_user_form() # whatever you do to create the form in the test
user_from.save()
mocked_generate_password.assert_called_once()
mocked_send_mail.assert_called_once()
If you now want to use the real generate_random_password, but still want to see if it was called, you can use mocker.spy instead:
def test_save_user(mocker):
mocked_generate_password = mocker.spy(some_module, 'generate_random_password')
mocked_send_mail = mocker.patch('some_module.send_mail')
user_form = create_user_form() # whatever you do to create the form in the test
user_from.save()
mocked_generate_password.assert_called_once()
mocked_send_mail.assert_called_once()
Note that you can achieve the same with unittest.mock.patch.object, but less convenient, in my opinion.

Django 2 Test response for valid and invalid form

I made a TestCase to check if I would get the proper response and page redirection, but It's not working as I thought it would. When I tried a valid form I got the response I expected, but when I made it invalid, I still got the same response.
views.py (I left off the 'GET' 'else:')
def create_employee_profile(request):
if request.POST:
name_form = EmployeeNameForm(request.POST)
if name_form.is_valid():
new_name_form = name_form.save()
return redirect(new_name_form)
else:
return render(request,
'service/create_or_update_profile.html',
{'name_form': name_form}
)
Test.py
class TestCreateEmployeeProfileView(TestCase):
def test_redirect_on_success(self):
response = self.client.post('/service/', {
'first_name': 'Test', # Required
'middile_name': 'Testy', # Optional
'last_name': '', # Required
})
self.assertEqual(response.status_code, 200)
I guess while I am question, I might as well ask how to access the redirect to test that as well.
On success, the new path should be /service/name/1/, the '1' being the 'pk' of the created object.
I know I've seen SimpleTestCase, but I haven't found a good example or tutorial on how to use it.
If you always get a 200, that is because your form is always invalid. Your view redirects on successful save, which is a 302.
The way to test that the form has saved is to check that the new item is indeed in the database:
self.assertTrue(Employee.objects.filter(first_name='Testy').exists())
or whatever.
Here are two scenarios:
Your form is valid, it will be saved and will be redirected to new_name_form that means a successful redirection. Since it is a successful redirection, you will get status code 200.
The same thing will happen when your form is invalid, i.e it will start rendering the create_or_update_profile page. Hence successful rendering and 200 status code.
So in either way, you will get successful redirection.
If you want to check the form, this is the better approach to do:
from form import EmployeeNameForm
class TestCreateEmployeeProfileView(TestCase):
def test_redirect_on_success(self):
form = UserForm(data='first_name': 'Test', # Required
'middile_name': 'Testy', # Optional
'last_name': '',)
self.assertTrue(form.is_valid())
def test_redirect_on_failure(self):
form = UserForm(data='first_name': 'Test', # Required
'middile_name': 'Testy', # Optional
'last_name': '',)
self.assertFalse(form.is_valid())
There will be no need to test the redirection. It surely will work fine, if the form is valid.
Hope that helped.

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)

django-activity-stream actions not displaying

I've just set django-activity-stream up but can't get it to display my actions when I goto the built in template mysite.com/activity/. Yet if I check the admin site I can see the actions have been saved as expected. I am using django-allauth for authentication/authorization
myapp/Settings.py
ACTSTREAM_SETTINGS = {
'MODELS': ('auth.user', 'auth.group'),
'MANAGER': 'actstream.managers.ActionManager',
'FETCH_RELATIONS': True,
'USE_PREFETCH': True,
'USE_JSONFIELD': True,
'GFK_FETCH_DEPTH': 0,
}
myapp/receivers.py
from actstream import action
#receiver(user_logged_in)
def handle_user_logged_in(sender, **kwargs):
request = kwargs.get("request")
user = kwargs['user']
action.send(user, verb='logged in')
In the django-activity-stream views.py it seems models.user_stream(request.user) is returning empty. But I have no idea why.
actstream/views.py
#login_required
def stream(request):
"""
Index page for authenticated user's activity stream. (Eg: Your feed at
github.com)
"""
return render_to_response(('actstream/actor.html', 'activity/actor.html'), {
'ctype': ContentType.objects.get_for_model(User),
'actor': request.user, 'action_list': models.user_stream(request.user)
}, context_instance=RequestContext(request))
Debugging from models.userstream(request.user) it seems I've found where it's returning no results:
actstream/managers.py
#stream
def user(self, object, **kwargs):
"""
Stream of most recent actions by objects that the passed User object is
following.
"""
q = Q()
qs = self.filter(public=True)
actors_by_content_type = defaultdict(lambda: [])
others_by_content_type = defaultdict(lambda: [])
follow_gfks = get_model('actstream', 'follow').objects.filter(
user=object).values_list('content_type_id',
'object_id', 'actor_only')
if not follow_gfks:
return qs.none()
When I check the value at q = self.filter I can actually see all the correct "logged in" activities for the user I passed, however when it gets to follow_gfks = get_model because the user in question isn't following anyone else follow_gfks ends up being None and the query set qs gets deleted on the last line.
Why this works this way when im just trying to view my own users activity feed I have no idea.
Here's what a row from my actstream_action table looks like:
id 1
actor_content_type_id [fk]3
actor_object_id 2
verb logged in
description NULL
target_content_type_id NULL
target_object_id NULL
action_object_content_type_id NULL
action_object_object_id NULL
timestamp 2013-09-28 12:58:41.499694+00
public TRUE
data NULL
I've managed to get the action/activity list of the current logged in user by passing user to actor_stream() instead of user_stream(). But I have no idea why user_stream doesn't work as intended
If it's your user that you want to show the actions for, you need to pass with_user_activity=True to user_stream; if it's for another user, you need to follow them first.