Fakultas Ilmu Komputer UI

Commit ef2ac92a authored by Rani Lasma Uli's avatar Rani Lasma Uli
Browse files

share differences between mocking manually and mocking using library, also fix some tests

parent c63485ed
Pipeline #25824 passed with stages
in 9 minutes and 53 seconds
......@@ -221,7 +221,92 @@ Hal ini akan membuat mutant menghasilkan kode response 302, sedangkan kode yang
#### Mocking Manually vs Library
Mock adalah sebuah objek yang meniru tingkah laku dari sebuah objek.
Dengan adanya Mock, developer dipermudah untuk fokus menguji method suatu method tanpa harus memikirkan method lain (Unit Test). Kegiatan mocking object dapat dilakukan dengan 2 cara yaitu, secara manual dan menggunakan bantuan library yang telah disediakan bahasa tertentu, dalam hal ini Python. Berikut adalah perbedaan antara mocking secara manual dengan menggunakan library.
1. Mocking Manually
Dalam chapter 19, saya telah membuat fungsi `fake_send_email` untuk mengimitasi fungsi `send_login_email`. Fungsi `send_login_email` melibatkan fungsi `send_mail` yang merupakan bawaan dari module django.core.mail. Secara sederhana, `send_login_email` melibatkan modul lain dan tidak memungkinkan untuk menguji `send_login_email` tanpa fungsi tersebut. Oleh karena itu, saya membuat fungsi `fake_send_email` dan mengalihkan pemanggilan kode yang pada awalnya memanggil `send_login_email` menjadi `fake_send_email`.
2. Mocking using Library
\ No newline at end of file
```python
def send_login_email(request):
email = request.POST['email']
token = Token.objects.create(email=email)
url = request.build_absolute_uri(f'/accounts/login?token={token.uid}')
# imported from django.core.mail
# need to be mocked
send_mail(
'Your login link for Superlists', # subject
f'Use this link to log in:\n\n{url}', # body
'noreply@superlists', # from_email
[email],
)
messages.success(
request,
"Check your email, we've sent you a link you can use to log in.")
return redirect('/')
```
accounts/views.py
```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
# send_mail() is replaced by fake_send_mail()
# HOW??
# account.views.send_mail means import method send_mail is replaced by fake_send_mail
# that's why import module should be put in the first line
accounts.views.send_mail = fake_send_mail
self.client.post('/accounts/send_login_email', data={
'email': 'edith@example.com'
})
self.assertTrue(mock_send_mail.called)
...
```
Finally, `fake_send_mail` successfully imitate `send_mail`. As you can see, `fake_send_mail` store all `send_mail` attributes in its attribute using `self`.
> Method has been mocked! So, are we done? Does it provide the best way to mock?
> How if other class need the exactly same mocking object?
> Now let's take a look to `Mocking using Library` section.
2. Mocking using Library
Now we do mocking the same method, `send_email`, using `unittest.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'})
# get all arguments those involved in mocking
# arguments value retrieve in tuples and sorted as send_email
(subject, body, from_email, to_list), kwargs = mock_send_mail.call_args
...
```
You can see how much lines we reduce.
Moreover, import modules in the middle of code will screwing up the code quality (not best practice).
Also, using library for mocking is pretty simple - in this case just using @patch(`modules_path`) and we're done - and make the mocking object become reusable.
> **Summary**
> Manual Mock:
> 1. Dirty Code
> 2. Redundant with the real function, we should re-construct real object in the fake method to do
> imitation.
>
> Mocking with Library:
> 1. Readable code, no object creation in testing class
> 2. Fully-package! No need to write the construct the object (fake) to mock real object
import os
import re
from unittest import skipIf
from django.core import mail
from selenium.webdriver.common.keys import Keys
from functional_tests.base import FunctionalTest
TEST_EMAIL = 'safira@example.com'
SUBJECT = 'Your login link for Superlists'
class LoginTest(FunctionalTest):
@skipIf(os.environ.get("HEROKU_HOST"),
"Somehow always fails at Heroku, outgoing SMTP blocked by Heroku")
def test_can_get_email_link_to_log_in(self):
# Safira goes to the awesome superlists site
# and notices a "Log in" section in the navbar for the first time
# It's telling her to enter her email address, so she does
self.browser.get(self.BASE_URL)
self.browser.find_element_by_name('email').send_keys(TEST_EMAIL)
self.browser.find_element_by_name('email').send_keys(Keys.ENTER)
# A message appears telling her an email has been sent
self.wait_for(lambda: self.assertIn(
'Check your email',
self.browser.find_element_by_tag_name('body').text))
# She checks her email and finds a message
email = mail.outbox[0]
self.assertIn(TEST_EMAIL, email.to)
self.assertEqual(email.subject, SUBJECT)
# It has a url link in it
self.assertIn('Use this link to log in', email.body)
url_search = re.search(r'http://.+/.+$', email.body)
if not url_search:
self.fail(f'Could not find url in email body:\n{email.body}')
url = url_search.group(0)
self.assertIn(self.BASE_URL, url)
# she clicks it
self.browser.get(url)
# she is logged in!
self.wait_for(
lambda: self.browser.find_element_by_link_text('Log out'))
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)
from unittest.mock import patch, call
from django.test import TestCase
class SendLoginEmailViewTest(TestCase):
def test_redirects_to_home_page(self):
response = self.client.post('/accounts/send_login_email',
data={'email': 'edith@example.com'})
self.assertRedirects(response, '/')
@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.assertTrue(mock_send_mail.called)
(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'])
def test_adds_success_message(self):
response = self.client.post('/accounts/send_login_email',
data={'email': 'edith@example.com'},
follow=True)
message = list(response.context['messages'])[0]
self.assertEqual(
message.message,
"Check your email, we've sent you a link you can use to log in.")
self.assertEqual(message.tags, "success")
@patch('accounts.views.auth')
class LoginViewTest(TestCase):
def test_calls_authenticate_with_uid_from_get_request(self, mock_auth):
self.client.get('/accounts/login?token=abcd123')
self.assertEqual(mock_auth.authenticate.call_args, call(uid='abcd123'))
import os
import re
from unittest import skipIf
from django.core import mail
from selenium.webdriver.common.keys import Keys
import re
from .base import FunctionalTest
from functional_tests.base import FunctionalTest
TEST_EMAIL = 'edith@example.com'
TEST_EMAIL = 'hahaha@xmail.com'
SUBJECT = 'Your login link for Superlists'
class LoginTest(FunctionalTest):
@skipIf(os.environ.get("HEROKU_HOST"),
"Somehow always fails at Heroku, outgoing SMTP blocked by Heroku")
def test_can_get_email_link_to_log_in(self):
# Safira goes to the awesome superlists site
# and notices a "Log in" section in the navbar for the first time
......@@ -45,10 +47,10 @@ 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()
# 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)
# 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)
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment