diff --git a/README.md b/README.md index f27ea92a66799a8f832c686f7cceb9630751e912..fb9af00037141916d7ceefaca264bb3c474757e9 100644 --- a/README.md +++ b/README.md @@ -131,4 +131,71 @@ Selanjutnya saya melakukan mutation testing dengan menggunakan django-mutpy. Den ## Exercise 7 -Pada exercise 7 kali ini, saya melakukan `spiking` dan `de-spiking` sesuai dari tutorial yang ada pada buku obeythetestinggoat. Spiking adalah proses pengimplementasian suatu prototype atau code tanpa melakukan implementasi TDD. Tutorial spiking yang saya lakukan bisa dilihat pada branch `exercise-7a`. Spiking sebaiknya dilakukan pada branch terpisah (bukan branch utama) karena hanya digunakan untuk mengecek apakah fungsionalitas dari fitur yang dibuat sudah berjalan dengan benar atau belum. Apabila sudah benar, maka akan dilakukan `de-spiking`. Pada implementasi `de-spiking`, code yang sudah dibuat pada `spiking` dihapus secara keseluruhan dan setelah itu code tersebut akan diimplementasikan ulang dengan menggunakan TDD yang benar. \ No newline at end of file +Pada exercise 7 kali ini, saya melakukan `spiking` dan `de-spiking` sesuai dari tutorial yang ada pada buku obeythetestinggoat. Spiking adalah proses pengimplementasian suatu prototype atau code tanpa melakukan implementasi TDD. Tutorial spiking yang saya lakukan bisa dilihat pada branch `exercise-7a`. Spiking sebaiknya dilakukan pada branch terpisah (bukan branch utama) karena hanya digunakan untuk mengecek apakah fungsionalitas dari fitur yang dibuat sudah berjalan dengan benar atau belum. Apabila sudah benar, maka akan dilakukan `de-spiking`. Pada implementasi `de-spiking`, code yang sudah dibuat pada `spiking` dihapus secara keseluruhan dan setelah itu code tersebut akan diimplementasikan ulang dengan menggunakan TDD yang benar. + + + +## Exercise 8 + +Ketika ingin melakukan unit test pada suatu kelas, terkadang kelas tersebut memiliki dependency atau object yang harus diikutsertakan jika ingin menjalankan fungsi yang ada dalam kelas tersebut. Untuk mengatasi hal ini, kita bisa mengganti dependency atau object lain yang diperlukan itu dengan mock yang bisa menyimulasikan behaviour dari dependency atau object yang sesungguhnya. + +Pada exercise 8 ini, saya belajar mengenai Manual Mocking dan juga Python Mock Library. + +### Manual Mocking + +```python + def test_sends_mail_to_address_from_post(self): + self.send_mail_called = False + + def fake_send_mail(subject, body, from_email, to_list): + self.send_mail_called = True + self.subject = subject + self.body = body + self.from_email = from_email + self.to_list = to_list + + accounts.views.send_mail = fake_send_mail + + self.client.post('/accounts/send_login_email', data={ + 'email': 'edith@example.com' + }) + + + self.assertTrue(self.send_mail_called) + self.assertEqual(self.subject, 'Your login link for Superlists') + self.assertEqual(self.from_email, 'noreply@superlists') + self.assertEqual(self.to_list, ['edith@example.com']) +``` + +Potongan code di atas adalah manual mocking yang saya lakukan pada exercise 8 ini. Yang ingin dilakukan pada potongan code di atas adalah melakukan unit test pada method `send_login_email` yang ada pada views. Manual mocking def `fake_send_mail` dibuat agar ketika unit test dijalankan, test tidak perlu benar-benar mengirimkan email. `fake_send_mail` memiliki behaviour yang sama dengan method `send_mail` sehingga method ini bisa menggantikan `send_mail` yang ada pada views. +Method send_mail yang ada pada views adalah seperti berikut: +```python + def send_login_email(request): + [...] + send_mail( + 'Your login link for Superlists', + message_body, + 'noreply@superlists', + [email] + ) + [...] + return render(request, 'login_email_sent.html') + +``` +### Python Mock Library + +```python + @patch('accounts.views.send_mail') + def test_sends_mail_to_address_from_post(self, mock_send_mail): + self.client.post('/accounts/send_login_email', data={ + 'email': 'edith@example.com' + }) + + self.assertEqual(mock_send_mail.called, True) + (subject, body, from_email, to_list), kwargs = mock_send_mail.call_args + self.assertEqual(subject, 'Your login link for Superlists') + self.assertEqual(from_email, 'noreply@superlists') + self.assertEqual(to_list, ['edith@example.com']) +``` + +Pada potongan code di atas terdapat anotasi patch yang digunakan untuk melakuan monkeypatching. Anotasi patch menerima notasi dot dari object yang ingin di monkeypatch. Arti `accounts.views.send_mail` yang ada pada code di atas akan menggantikan `send_mail` yang ada pada views. Karena itu ketika test dijalankan, `send_mail` tidak akan dijalankan, akan tetapi `send_mail_mock` yang akan menggantikannya. \ No newline at end of file diff --git a/accounts/migrations/0001_initial.py b/accounts/migrations/0001_initial.py index 534bfa236ca8bbdc29d7b357722beba54287093b..7f16f34a96d58e9afd184a62fb186e1a735774b1 100644 --- a/accounts/migrations/0001_initial.py +++ b/accounts/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.24 on 2019-11-13 07:31 +# Generated by Django 1.11.24 on 2019-11-20 08:17 from __future__ import unicode_literals from django.db import migrations, models diff --git a/accounts/models.py b/accounts/models.py index 24249e1f1694321d0c13ccfb9eace8dd8e8495e3..7425aac405ccdd9c91858120be5514d8c34c1ac6 100644 --- a/accounts/models.py +++ b/accounts/models.py @@ -1,5 +1,8 @@ from django.db import models import uuid +from django.contrib import auth + +auth.signals.user_logged_in.disconnect(auth.models.update_last_login) class User(models.Model): email = models.EmailField(primary_key=True) diff --git a/accounts/tests/test_models.py b/accounts/tests/test_models.py index ae2ee6be030be510c110e34fa60277c29a1ea352..f68f58466d81cca8ebd7e9640f6fc585e09b432a 100644 --- a/accounts/tests/test_models.py +++ b/accounts/tests/test_models.py @@ -1,6 +1,7 @@ from django.test import TestCase from django.contrib.auth import get_user_model from accounts.models import Token +from django.contrib import auth User = get_user_model() @@ -15,6 +16,13 @@ class UserModelTest(TestCase): user = User(email='a@b.com') self.assertEqual(user.pk, 'a@b.com') + def test_no_problem_with_auth_login(self): + user = User.objects.create(email='edith@example.com') + user.backend = '' + request = self.client.request().wsgi_request + auth.login(request, user) # should not raise + + class TokenModelTest(TestCase): def test_links_user_with_auto_generated_uid(self): diff --git a/accounts/tests/test_views.py b/accounts/tests/test_views.py index 9fee1d3bba2c4e952f0c87f88486aa7225a515ec..2afec013ea57950b0f879a7b96ea1afdb9f991d4 100644 --- a/accounts/tests/test_views.py +++ b/accounts/tests/test_views.py @@ -9,7 +9,7 @@ class SendLoginEmailViewTest(TestCase): response = self.client.post('/accounts/send_login_email', data={ 'email': 'edith@example.com' }) - self.assertRedirects(response, '/') + self.assertTemplateUsed(response, 'login_email_sent.html') def test_sends_mail_to_address_from_post(self): self.send_mail_called = False diff --git a/accounts/urls.py b/accounts/urls.py index 6f49f26f8dcad7a4db92e7b57c14c562b8d2cbed..c715dd4dc5c8acfc88102ba8d5bc2c8827faf0af 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -1,7 +1,9 @@ from django.conf.urls import url from accounts import views +from django.contrib.auth.views import logout urlpatterns = [ url(r'^send_login_email$', views.send_login_email, name='send_login_email'), url(r'^login$', views.login, name='login'), + url(r'^logout$', logout, {'next_page': '/'}, name='logout'), ] diff --git a/accounts/views.py b/accounts/views.py index 84d23c34891d7c66443150901a94ec7db4fb4206..ebd0a0947622bbf071365b945508cdd4df0401b0 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -3,7 +3,7 @@ import sys from django.contrib.auth import authenticate from django.contrib.auth import login as auth_login from django.core.mail import send_mail -from django.shortcuts import redirect +from django.shortcuts import redirect, render from django.contrib import auth, messages from django.urls import reverse from accounts.models import Token @@ -26,13 +26,10 @@ def send_login_email(request): request, "Check your email, we've sent you a link you can use to log in." ) - return redirect('/') + return render(request, 'login_email_sent.html') def login(request): user = auth.authenticate(uid=request.GET.get('token')) if user: auth.login(request, user) - return redirect('/') - # user = auth.authenticate(uid=request.GET.get('token')) - # auth.login(request, user) - # return redirect('/') \ No newline at end of file + return redirect('/') \ No newline at end of file diff --git a/functional_tests/test_login.py b/functional_tests/test_login.py index 4172e3a7d4ccc85a7d1bc0f3607421559c115699..a6349ef48c3e60e98476d4a5c21890846147186a 100644 --- a/functional_tests/test_login.py +++ b/functional_tests/test_login.py @@ -11,7 +11,7 @@ SUBJECT = 'Your login link for Superlists' class LoginTest(FunctionalTest): - @skip + def test_can_get_email_link_to_log_in(self): # Edith goes to the awesome superlists site # and notices a "Log in" section in the navbar for the first time @@ -48,3 +48,13 @@ class LoginTest(FunctionalTest): ) navbar = self.browser.find_element_by_css_selector('.navbar') self.assertIn(TEST_EMAIL, navbar.text) + + # Now she logs out + self.browser.find_element_by_link_text('Log out').click() + + # She is logged out + self.wait_for( + lambda: self.browser.find_element_by_name('email') + ) + navbar = self.browser.find_element_by_css_selector('.navbar') + self.assertNotIn(TEST_EMAIL, navbar.text) diff --git a/lists/templates/base.html b/lists/templates/base.html index 75b12ddc09914b81c12b725bd85c520f8c321123..82a3de4f38f32b02149565e0311bf7286ae20270 100644 --- a/lists/templates/base.html +++ b/lists/templates/base.html @@ -28,11 +28,18 @@