Unlocking the Power of Mocking: Improving Code Quality and Reducing Development Time

Rizky Juniastiar
15 min readMay 11, 2023

--

Mocking and stubbing in testing is like creating a double of reality — a stand-in that allows you to test your code without relying on external dependencies. By isolating the system under test, you gain control over the test environment and can more easily reproduce and diagnose issues. With the power of mocking and stubbing, testing becomes not just a means of verifying functionality, but a tool for improving code quality and reducing development time.

Source: https://storyset.com/illustration/version-control/cuate

Di dunia bisnis yang serba cepat ini diperlukan sikap adaptif terhadap perubahan yang tinggi. Perusahaan-perusahaan saling berlomba untuk dapat menghasilkan produk terbaik yang dapat memudahkan kehidupan para penggunanya. Untuk menghasilkan produk perangkat lunak yang memiliki kualitas baik dan mudah untuk di-maintain pengujian (testing) terhadap perangkat lunak merupakan aspek penting yang tidak dapat dihindarkan. Terdapat istilah dalam aspek testing software yang bernama mocking dan stubbing. Lantas apa makna dari istilah-istilah tersebut?

Let’s dive into it! ✨

Software Testing

What is Software Testing?

Software testing merupakan salah satu proses penting dalam pengembangan perangkat lunak (software development) yang bertujuan untuk menjamin kualitas dan keandalan dari perangkat lunak tersebut. Testing dilakukan untuk memastikan bahwa perangkat lunak berfungsi dengan baik sesuai dengan spesifikasi dan kebutuhan pengguna serta bebas dari bug atau masalah lainnya.

Why software testing is important?

Software testing penting untuk dilakukan agar proses identifikasi bug atau masalah serta penangannya dapat dilakukan lebih awal saat masa development sehingga product yang di-deliver kepada pengguna akan memiliki kualitas yang baik karena sudah dilakukan pengujian sebelumnya. Produk perangkat lunak yang diuji dengan benar akan memiliki keandalan (reliability), keamanan, dan kinerja tinggi yang selanjutnya dapat menghemat waktu pengembangan produk serta menghasilkan efektivitas biaya dan kepuasan dari pelanggan.

Levels of Software Testing

levels of testing (src)

Dalam pengembangan perangkat lunak, terdapat empat level testing yang biasa digunakan untuk memastikan kualitas perangkat lunak secara menyeluruh. Keempat level testing ini meliputi:

  1. Unit Testing: Level pertama dari testing adalah unit testing. Test ini fokus pada pengujian komponen atau unit kecil dari perangkat lunak. Unit testing dilakukan dengan mengisolasi unit tersebut dari bagian lain dari perangkat lunak dan menjalankan serangkaian tes pada unit tersebut untuk memverifikasi apakah unit tersebut berfungsi dengan benar atau tidak.
  2. Integration Testing: Setelah unit testing, level selanjutnya adalah integration testing. Test ini bertujuan untuk menguji bagaimana komponen atau unit yang berbeda-beda bekerja bersama sebagai satu kesatuan dalam perangkat lunak. Dalam integration testing, pengujian dilakukan pada interaksi antar unit dan bagaimana unit tersebut saling berkomunikasi dan berintegrasi satu sama lain.
  3. System Testing: Level berikutnya adalah system testing. Test ini melibatkan pengujian perangkat lunak secara keseluruhan sebagai sebuah sistem yang utuh. Dalam system testing, pengujian dilakukan pada sistem perangkat lunak secara keseluruhan, termasuk interaksi antara komponen dan antara sistem dan pengguna.
  4. Acceptance Testing: Level testing terakhir adalah acceptance testing. Test ini bertujuan untuk memastikan bahwa perangkat lunak memenuhi spesifikasi dan kebutuhan pengguna. Acceptance testing dilakukan oleh pihak pengguna atau klien, dan bertujuan untuk menguji apakah perangkat lunak siap untuk digunakan secara nyata.

Dalam pengembangan perangkat lunak, keempat level testing ini saling berkaitan dan harus dilakukan secara bertahap dan terintegrasi untuk memastikan bahwa perangkat lunak berfungsi dengan baik dan memenuhi requirements yang diminta. Melalui penggunaan level testing yang sistematis, perangkat lunak dapat diuji secara menyeluruh dan masalah atau kesalahan dalam perangkat lunak dapat diidentifikasi dan diperbaiki lebih awal dalam proses pengembangan.

Untuk bagian selanjutnya, kita akan lebih fokus untuk membahas unit testing. Let’s dig in!

Unit Testing

Unit Testing adalah salah satu jenis software testing yang lebih berfokus untuk menguji komponen atau unit kecil dari perangkat lunak. Unit yang dimaksud di sini dapat berupa fungsi, metode, prosedur, modul, atau objek individual. Tujuan dari metode ini adalah untuk menguji atau memvalidasi apakah tiap komponen atau unit kode dari perangkat lunak berfungsi atau berperilaku sesuai dengan yang diharapkan.

Pada tahap testing ini, setiap komponen diuji sendiri-sendiri secara terisolasi tanpa melibatkan unit test atau komponen lain yang mungkin dalam implementasi sebeanarnya terlibat.

Benefits of unit testing

Berikut adalah beberapa manfaat melakukan unit testing, di antaranya adalah sebagai berikut.

  • Menghasilkan kode yang memiliki kualitas yang sudah teruji baik
  • Unit test membantu memperbaiki bug di awal siklus pengembangan sehingga dapat menghemat biaya.
  • Membantu pengembang untuk memahami basis kode pengujian dan memungkinkan mereka membuat perubahan dengan cepat
  • Menghasilkan kode yang lebih mudah untuk di-reuse. Hal tersebut karena kode yang sudah dilakukan unit test sudah terjamin kualitas dan perilaku atau spesifikasinya.

Pada unit test kita ingin menguji tiap komponen atau unit kecil dari suatu kode secara terisolasi. Lantas bagaimana caranya? Let’s dive into it!

Test Doubles

Test Doubles adalah istilah umum yang digunakan untuk menyebut objek-objek palsu yang digunakan dalam pengujian perangkat lunak. Istilah ini diperkenalkan oleh Gerard Meszaros dan merujuk pada konsep Stunt Double dalam dunia perfilman. Tujuan dari Test Doubles adalah untuk mengisolasi unit pengujian dari dependensi eksternal sehingga memungkinkan pengujian yang lebih cepat, lebih fokus, dan lebih andal.

Berdasarkan artikel Martin Flower berikut, terdapat lima jenis test doubles, yaitu:

  • Dummy: Dummy object adalah objek yang hanya digunakan untuk memenuhi requirements parameter, tetapi tidak memiliki perilaku yang berguna dalam pengujian.

Berikut adalah contoh implementasi dari dummy object.

from app.models import Order, Customer, Cart

def test_order_can_be_saved():
# Membuat objek dummy untuk Customer dan Cart
customer = DummyCustomer()
cart = DummyCart()
# Membuat objek Order dengan menggunakan objek dummy yang telah dibuat
order = Order(customer, cart)

# Memanggil method save() dan memastikan bahwa order berhasil disimpan
assert order.save() == True
class DummyCustomer:
pass
class DummyCart:
pass

Pada contoh tersebut, objek dummy untuk Customer dan Cart dibuat dengan membuat kelas-kelas kosong DummyCustomer dan DummyCart. Objek-objek dummy ini kemudian digunakan untuk membuat objek Order yang akan diuji apakah berhasil disimpan dengan memanggil method save(). Dalam hal ini, kita tidak perlu melakukan apa pun dalam definisi DummyCustomer dan DummyCart karena tidak ada parameter atau behavior yang diperlukan dalam fungsi test_order_can_be_saved().

Pada dasarnya, penggunaan objek dummy di sini adalah untuk mengecek apakah objek Order dapat disimpan ke database dengan benar tanpa harus membuat objek Customer dan Cart yang benar-benar ada dan dapat dioperasikan.

  • Fake: Fake Object adalah objek palsu yang memiliki implementasi fungsionalitas sebenarnya, tetapi tidak sepenuhnya seperti objek sebenarnya dan tidak cocok untuk penggunaan produksi. Fake Object digunakan untuk menggantikan objek yang kompleks dan lambat dengan objek sederhana dan cepat yang dapat digunakan dalam pengujian.

Contoh kasus dari penggunaan fake misalnya kita ingin menguji fungsi yang berinteraksi dengan database. Karena operasi database bisa sangat lambat dan kompleks, kita dapat menggunakan Fake Object yang mengimplementasikan operasi database secara sederhana, seperti menyimpan data dalam struktur data sementara di memori, bukan ke database yang sebenarnya. Berikut adalah contoh implementasinya.

from app.models import Order, Customer, Cart

class FakeCustomer(Customer):
def __init__(self):
self.id = 1
self.name = "John Doe"
self.email = "johndoe@example.com"

class FakeCart(Cart):
def __init__(self):
self.items = [
{'product': 'item_1', 'price': 100},
{'product': 'item_2', 'price': 200},
{'product': 'item_3', 'price': 300}
]

class FakeOrder(Order):
def __init__(self):
self.customer = FakeCustomer()
self.cart = FakeCart()
def save(self):
# Implementasi penyimpanan data sementara
return True

def test_order_can_be_saved():
# Membuat objek FakeOrder
order = FakeOrder()
# Memanggil method save() dan memastikan bahwa order berhasil disimpan
assert order.save() == True

Pada contoh tersebut dibuat Fake Object untuk Customer, Cart, dan Order. Implementasi objek-objek palsu ini sederhana dan hanya mengekspor beberapa properti untuk memungkinkan pengujian. Objek FakeOrder kemudian digunakan dalam fungsi pengujian test_order_can_be_saved() untuk mengecek apakah objek dapat disimpan dengan benar.

Fake Object memungkinkan pengujian lebih cepat dan mudah dilakukan karena kita tidak perlu mengoperasikan sistem yang sebenarnya, misalnya mengakses data di database sebenarnya. Namun, perlu diingat bahwa penggunaan Fake Object harus dilakukan dengan hati-hati karena dapat menyebabkan kesalahan jika fungsionalitas yang penting tidak diimplementasikan dengan benar.

  • Stub: Stub adalah jenis Test Double yang memberikan respons tertentu untuk pemanggilan metode tertentu dan biasanya tidak memberikan respons untuk pemanggilan metode lainnya. Stub digunakan ketika kita ingin mengisolasi unit pengujian dari dependensi eksternal sehingga memungkinkan pengujian yang lebih cepat dan lebih fokus.

Berikut ini adalah contoh implementasinya.

class PaymentGateway:
def process_payment(self, amount):
pass

class Order:
def __init__(self, payment_gateway):
self.payment_gateway = payment_gateway

def checkout(self, total_amount):
response = self.payment_gateway.process_payment(total_amount)
return response

class StubPaymentGateway(PaymentGateway):
def process_payment(self, amount):
return {'status': 'success'}

def test_order_can_be_checked_out():
order = Order(StubPaymentGateway())
response = order.checkout(100)
assert response['status'] == 'success'

Pada contoh di atas, terdapat kelas PaymentGateway dan Order. PaymentGateway adalah sebuah kelas yang bertanggung jawab untuk memproses pembayaran, sementara Order adalah kelas yang memproses proses pembayaran untuk suatu pesanan.

Untuk mengisolasi unit pengujian dari dependensi eksternal, kita membuat StubPaymentGateway sebagai pengganti PaymentGateway. StubPaymentGateway merupakan subclass dari PaymentGateway dan hanya mengimplementasikan method process_payment() dengan memberikan respons sukses tanpa melakukan operasi apapun.

Kemudian, pada unit test test_order_can_be_checked_out(), kita membuat sebuah instance dari Order dengan menggunakan StubPaymentGateway sebagai argument, sehingga pada saat method checkout() dipanggil, tidak ada akses ke PaymentGateway yang sebenarnya. Kita juga menguji respons yang diberikan oleh StubPaymentGateway apakah sesuai dengan yang diharapkan.

  • Spy: Spy Object adalah jenis Test Double yang mirip dengan Stub, tetapi selain memberikan respons tertentu untuk pemanggilan metode tertentu, Spy juga merekam informasi tambahan, seperti jumlah pemanggilan, argumen yang digunakan, atau nilai kembalian. Spy digunakan ketika kita ingin memastikan bahwa metode tertentu telah dipanggil dengan argumen tertentu dan seberapa sering dipanggil.

Berikut adalah contoh implementasinya.

class User:
def __init__(self, name, age):
self.name = name
self.age = age

class UserRepository:
def __init__(self):
self.users = []
def add_user(self, user):
self.users.append(user)
def get_users(self):
return self.users

class SpyUserRepository(UserRepository):
def __init__(self):
super().__init__()
self.add_user_calls = []
def add_user(self, user):
self.add_user_calls.append(user)

def test_user_repository_can_add_user():
spy_repository = SpyUserRepository()
user = User('John Doe', 25)
spy_repository.add_user(user)
assert len(spy_repository.add_user_calls) == 1
assert spy_repository.add_user_calls[0].name == 'John Doe'
assert spy_repository.add_user_calls[0].age == 25

Pada contoh di atas, terdapat kelas User dan UserRepository. User adalah sebuah kelas yang merepresentasikan seorang pengguna dengan atribut nama dan usia, sementara UserRepository adalah kelas yang bertanggung jawab untuk menyimpan dan mengembalikan daftar pengguna.

Untuk menguji apakah method add_user() pada UserRepository berfungsi dengan baik, kita membuat SpyUserRepository sebagai pengganti UserRepository. SpyUserRepository merekam setiap pemanggilan metode add_user() dengan menyimpan argumen yang digunakan.

Pada unit test test_user_repository_can_add_user(), kita membuat sebuah instance dari SpyUserRepository dan menambahkan seorang pengguna baru. Kita juga memeriksa apakah SpyUserRepository merekam pemanggilan metode add_user() dengan benar dan apakah argumen yang digunakan sesuai dengan yang diharapkan. Dengan demikian, kita dapat memastikan bahwa UserRepository berfungsi dengan benar.

  • Mock: Mock Object adalah jenis Test Double yang mirip dengan Spy, tetapi memiliki kemampuan tambahan yaitu dapat memeriksa apakah metode yang diharapkan dipanggil dengan argumen yang diharapkan, dan mengembalikan nilai yang diharapkan. Mock digunakan ketika kita ingin menguji bagaimana sebuah objek berinteraksi dengan objek lain yang mungkin tidak tersedia selama pengujian.

Kita akan membahas lebih lanjut mock dan implementasinya pada python pada bagian selanjutnya.

Let’s dig in! ✨

Mock

Mock Illustration (src)

Mock adalah salah satu teknik dalam pengujian perangkat lunak yang digunakan untuk menggantikan objek atau komponen perangkat lunak yang tidak dapat diakses atau terlalu kompleks untuk diuji secara langsung dalam lingkungan pengujian. Dalam teknik mock, objek atau komponen perangkat lunak yang sebenarnya digantikan dengan objek palsu atau tiruan yang dibuat secara sederhana dan dapat dikontrol dalam pengujian.

Objek tiruan ini dibuat dengan tujuan untuk mensimulasikan perilaku objek asli dalam pengujian. Dengan teknik mock, developer dapat mengisolasi unit atau komponen tertentu dalam perangkat lunak dan menguji fungsionalitasnya secara terpisah.

Dalam pengujian perangkat lunak, teknik mock sering digunakan bersama dengan teknik stubbing untuk mempermudah proses pembuatan objek tiruan dan mensimulasikan perilaku objek yang berbeda dalam pengujian. Teknik mock dapat membantu pengembang untuk meningkatkan kualitas perangkat lunak dengan mengidentifikasi dan memperbaiki kesalahan atau bug yang mungkin terjadi dalam unit atau komponen perangkat lunak.

When to use mock?

  • Ketika terdapat ketergantungan pada sistem atau layanan eksternal: Ketika suatu unit bergantung pada layanan eksternal, maka dalam lingkungan pengujian unit sangat sulit untuk menguji unit tersebut secara langsung. Dalam situasi seperti ini, teknik mocking berguna untuk mensimulasikan respons dari layanan eksternal tersebut sehingga pengujian unit bisa dilakukan lebih baik.

Berikut adalah contoh implementasi skenario ini dengan django.

import requests

class Flight:
def __init__(self, flight_number, origin, destination):
self.flight_number = flight_number
self.origin = origin
self.destination = destination

def get_flight_info(self):
url = f'https://example.com/api/flight/{self.flight_number}'
response = requests.get(url)
if response.status_code == 200:
return response.json()
return None

def get_departure_time(self):
flight_info = self.get_flight_info()
if flight_info:
return flight_info['departure_time']
return None

Perhatikan bahwa method get_flight_info() menggunakan layanan eksternal dalam implementasinya. Misalkan kita ingin membuat unit test untuk method get_departure_time() yang di dalamnya memanggil method get_flight_info() yang menggunakan layanan eksternal. Berikut adalah implementasi unit test-nya dengan mock.

from unittest.mock import patch
from flight import Flight

class TestFlight:
def test_get_departure_time(self):
with patch('flight.requests.get') as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {'departure_time': '2023-05-15 12:00:00'}
flight = Flight('SU123', 'Moscow', 'Jakarta')
departure_time = flight.get_departure_time()
assert departure_time == '2023-05-15 12:00:00'

Pada contoh tersebut, kita menggunakan decorator patch dari modul unittest.mock untuk mensimulasikan respons dari layanan requests.get(). Untuk kasus ini, kita “mengatur” atau mensimulasikan return value status code dari pemanggilan layanan eksternal menjadi 200 dan json return value menjadi {‘departure_time’: ‘2023–05–15 12:00:00’} dari mock object untuk mensimulasikan informasi penerbangan yang diambil dari layanan eksternal. Setelah itu, kita membuat object Flight dan memanggil method get_departure_time() kemudian mengevaluasi return value dari method tersebut apakah sudah sesuai yang diharapkan atau belum.

  • Ketika suatu method melakukan komputasi yang kompleks atau memerlukan sumber daya yang besar: Pada saat melakukan unit testing pada algoritma atau fungsi yang memerlukan sumber daya yang besar, seperti pengolahan gambar atau komputasi statistik yang kompleks, kita dapat menggunakan mock untuk menggantikan pemanggilan fungsi yang memerlukan sumber daya tersebut dengan hasil yang sudah diketahui sebelumnya. Dengan menggunakan mock, kita dapat menghindari waktu yang dibutuhkan untuk melakukan pemrosesan dan mempercepat waktu yang dibutuhkan untuk menjalankan unit testing.

Berikut adalah contoh implementasi dari skenario ini dengan menggunakan django.

views.py

from django.http import HttpResponse
from PIL import Image, ImageDraw

def generate_image(request):
# some complex computations to generate image
img = Image.new('RGB', (100, 100), color='red')
d = ImageDraw.Draw(img)
d.text((10, 10), "Hello World", fill=(255, 255, 255))
# save image to file
img.save('/path/to/image.png')
# return image as http response
with open('/path/to/image.png', 'rb') as f:
response = HttpResponse(f.read(), content_type='image/png')
return response

Pada contoh di atas, terdapat method generate_image() yang melakukan komputasi kompleks untuk menghasilkan sebuah gambar, kemudian menyimpannya ke file, dan mengembalikan file tersebut sebagai HTTP ressponse. Proses running method tersebut pastilah memakan waktu yang cukup lama karena proses komputasi atau implementasi yang dilakukan cukup kompleks.

Untuk menghindari waktu yang dibutuhkan untuk melakukan pemrosesan, kita dapat menggunakan mock untuk menggantikan pemanggilan fungsi Image.new(), ImageDraw.Draw(), dan img.save() dengan hasil yang sudah diketahui sebelumnya. Berikut adalah contoh implementasi test menggunakan mock:

tests.py

from django.test import TestCase
from unittest.mock import patch, MagicMock
from PIL import Image, ImageDraw
from io import BytesIO

class TestViews(TestCase):
def test_generate_image(self):
with patch('myapp.views.Image.new') as mock_new, \
patch('myapp.views.ImageDraw.Draw') as mock_draw, \
patch('myapp.views.open', create=True) as mock_open:
# set return value for mock objects
mock_img = MagicMock(spec=Image.Image)
mock_new.return_value = mock_img
mock_draw_obj = MagicMock(spec=ImageDraw.ImageDraw)
mock_draw.return_value = mock_draw_obj
mock_file = MagicMock(spec=BytesIO)
mock_open.return_value.__enter__.return_value = mock_file

response = self.client.get('/generate_image/')
# check response status code
self.assertEqual(response.status_code, 200)
# check that mock objects are called with expected parameters
mock_new.assert_called_once_with('RGB', (100, 100), color='red')
mock_draw.assert_called_once_with(mock_img)
mock_draw_obj.text.assert_called_once_with((10, 10), "Hello World", fill=(255, 255, 255))
mock_file.write.assert_called_once_with(mock.ANY)

Pada kode di atas, kita menggunakan patch() untuk menggantikan pemanggilan fungsi Image.new(), ImageDraw.Draw(), dan open() dengan mock object. Kemudian, kita menetapkan nilai kembalian dari mock object tersebut dan melakukan pengujian terhadap fungsi generate_image(). Hasil yang diharapkan dari pengujian ini adalah status code 200 dan pemanggilan fungsi Image.new(), ImageDraw.Draw(), dan open() dengan parameter yang diharapkan.

Mock Assertion in Django

Berikut adalah beberapa jenis mock assertion yang umum digunakan pada Django:

  1. assert_called_once() / assert_called_once_with() Assertion ini digunakan untuk mengecek apakah sebuah fungsi/mock object dipanggil tepat satu kali (assert_called_once()) atau dipanggil tepat satu kali dengan argumen yang tepat (assert_called_once_with()).
  2. assert_called() / assert_not_called() Assertion ini digunakan untuk mengecek apakah sebuah fungsi/mock object dipanggil minimal satu kali (assert_called()) atau tidak dipanggil sama sekali (assert_not_called()).
  3. assert_any_call() Assertion ini digunakan untuk mengecek apakah sebuah fungsi/mock object dipanggil minimal satu kali dengan argumen apapun.
  4. assert_has_calls() Assertion ini digunakan untuk mengecek apakah sebuah fungsi/mock object dipanggil dengan urutan pemanggilan yang tepat dan dengan argumen yang tepat.
  5. assert_has_awaits(*args, **kwargs) - Digunakan untuk memeriksa apakah metode async mock telah menunggu kembalian nilai dari objek yang di-await.
  6. assert_not_awaited() - Digunakan untuk memeriksa apakah metode async mock tidak menunggu kembalian nilai dari objek yang di-await.

Benefits of Mock

Berikut adalah beberapa manfaat dari penggunaan mock pada pengembangan perangkat lunak:

  1. Mempercepat unit testing: Mock dapat digunakan untuk menggantikan objek yang kompleks dan memerlukan waktu untuk dibuat atau diinisialisasi. Dengan menggunakan mock, unit testing dapat dilakukan tanpa mengalami penurunan kinerja karena harus mengakses objek tersebut secara langsung.
  2. Memungkinkan isolasi: Mock memungkinkan pengujian unit untuk dilakukan dengan lebih mudah dan tanpa harus tergantung pada komponen eksternal. Dengan mengisolasi unit testing dari ketergantungan dengan komponen eksternal, pengembang dapat fokus melihat implementasi suatu komponen apakah sudah sesuai atau belum.
  3. Memudahkan pengujian kasus yang sulit atau tidak mungkin: Dalam beberapa kasus, sulit untuk memicu atau menghasilkan kondisi kesalahan yang diperlukan untuk menguji aplikasi dengan unit testing. Dalam hal ini, mock dapat digunakan untuk memicu kondisi sulit atau menghasilkan kesalahan yang diperlukan untuk pengujian.
  4. Memperbaiki kualitas perangkat lunak: Dengan penggunaan mock, pengujian dapat dilakukan secara lebih cepat dan efektif, dan dapat membantu mengidentifikasi masalah, bug, dan kelemahan pada perangkat lunak sebelum di-release pada production.
  5. Memudahkan testing objek eksternal yang menghasilkan hasil nondeterministik: Implementasi method yang memanfaatkan return value dari objek eksternal yang menghasilkan hasil nondeterministik terlihat cukup mustahil untuk diimplementasikan. Akan tetapi, dengan menggunakan mock kita dapat menguji method tersebut. Mock dapat membuat skenario return value tetap untuk objek eksternal tersebut yang kemudian method yang memanggilnya dapat diuji pula perilakunya.

Penerapan Mock pada proyek SI-Event

auth_sso/utils.py

import requests

def get_sso_ui_data(username, password):
return requests.post(
'https://api.cs.ui.ac.id/authentication/ldap/v2/',
data = {
'username':username,
'password':password
}
)

Pada code tersebut terlihat bahwa method memanggil API eksternal dalam implementasinya. Untuk mengisolasi method tersebut, maka digunakan mocking yang mensimulasikan pemanggilan API eksternal tersebut. Mock diatur agar memberikan response status code 200 dan json return value {‘token’: ‘xyz123’}.

tests.py

class TestGetSSOUIData(TestCase):

@patch('auth_sso.utils.requests.post')
def test_get_sso_ui_data(self, mock_post):
# Set up the mock response
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {'token': 'xyz123'}
mock_post.return_value = mock_response

# Call the function with some test data
response = get_sso_ui_data('test_user', 'test_password')

# Check that the mock was called with the correct arguments
mock_post.assert_called_once_with(
'https://api.cs.ui.ac.id/authentication/ldap/v2/',
data = {
'username': 'test_user',
'password': 'test_password'
}
)

# Check that the function returns the expected value
self.assertEqual(response.status_code, 200)
self.assertEqual(response.json(), {'token': 'xyz123'})

Berikut contoh implementasi lainnya.

auth_sso/views.py

@require_http_methods(["GET", "POST"])
def login_sso(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']

sso_data = get_sso_ui_data(
username=username,
password=password
).json()

if sso_data['state'] == 0:
messages.info(request, 'Wrong SSO UI credentials')
return redirect('auth_sso:login_sso')
else:
# Account belum terbuat
if not SSOUIAccount.objects.filter(username=username).exists():
email = f'{username}@ui.ac.id'
user = User.objects.create_user(username=username, password=password, email=email)
account_sso = SSOUIAccount(
user = user,
kode_identitas = sso_data['kodeidentitas'],
nama = sso_data['nama'],
kode_organisasi = sso_data['kode_org'].split(":")[0],
username = username,
role = 'Guest'
)
acc = Account(
user = user,
accSSO = account_sso,
username = username,
email = email,
role = 'Guest',
accountType = 'SSO UI'
)
user.save()
account_sso.save()
acc.save()

user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('/home')

context = {'form':'form'}
return render(request, 'login_sso.html', context)

Pada code tersebut terlihat bahwa dalam implementasinya method memanggil komponen atau method di luar class, yaitu method get_sso_data() yang ada pada file utils.py. Untuk mengisolasi pengujian method tersebut, dibuat mock object yang memberikan return json untuk mensimulasikan pemanggilan method eksternal tersebut. Berikut adalah positive dan negative test dengan mock untuk code tersebut.

tests.py

from django.test import TestCase, Client
from unittest.mock import patch, Mock
from .models import SSOUIAccount
from account.models import User
from django.urls import reverse
from django.contrib.messages import get_messages

SSO_USERNAME = 'rizky.juniastiar'

class LoginSSOTestCase(TestCase):

def setUp(self):
self.client = Client()
self.url = reverse('auth_sso:login_sso')
self.username = SSO_USERNAME
self.password = 'passwordsso'

# positive test case
@patch('auth_sso.views.get_sso_ui_data')
def test_sso_successfully_login(self, mock_sso_data):
mock_sso_data.return_value.json.return_value = {
"username": SSO_USERNAME,
"nama": "Rizky Juniastiar",
"state": 1,
"kode_org": "09.00.12.01:mahasiswa",
"kodeidentitas": "2006596043",
"nama_role": "mahasiswa"
}
response = self.client.post(self.url, {
'username': self.username,
'password': self.password
})
self.assertEqual(response.url, '/home')
self.assertEqual(response.status_code, 302)

# Assert that a new SSOUIAccount and User were created
self.assertEqual(SSOUIAccount.objects.count(), 1)
self.assertEqual(User.objects.count(), 1)

# Assert that the created user has the correct attributes
user = User.objects.first()
self.assertEqual(user.username, self.username)
self.assertEqual(user.email, f'{self.username}@ui.ac.id')

# Assert that the SSOUIAccount was created with the correct attributes
sso_account = SSOUIAccount.objects.first()
self.assertEqual(sso_account.user, user)
self.assertEqual(sso_account.kode_identitas, '2006596043')
self.assertEqual(sso_account.nama, 'Rizky Juniastiar')
self.assertEqual(sso_account.kode_organisasi, '09.00.12.01')
self.assertEqual(sso_account.username, self.username)
self.assertEqual(sso_account.role, 'Guest')

# negative test case
@patch('auth_sso.views.get_sso_ui_data')
def test_sso_invalid_credentials(self, mock_sso_data):
mock_sso_data.return_value.json.return_value = {
'state': 0
}
response = self.client.post(self.url, {
'username': self.username,
'password': self.password
})
self.assertEqual(response.url, self.url)
self.assertEqual(response.status_code, 302)
messages = [msg.message for msg in get_messages(response.wsgi_request)]
self.assertEqual(messages[0], 'Wrong SSO UI credentials')

# Assert that no new SSOUIAccount or User were created
self.assertEqual(SSOUIAccount.objects.count(), 0)
self.assertEqual(User.objects.count(), 0)

Conclusion

Produk yang dapat bersaing di dunia yang serba cepat adalah produk yang berkualitas baik dan mudah untuk di-maintain. Hal tersebut mengakibatkan proses testing menjadi suatu keharusan untuk seluruh produk yang dibuat. Testing dapat membuat kode menjadi lebih terjamin kualitasnya. Testing terdiri atas empat level, mulai dari unit testing, integration testing, system testing, hingga acceptance testing. Unit testing adalah pengujian komponen atau unit kecil dari suatu kode. Unit testing harus dilakukan terhadap seluruh komponen secara terisolasi. Pengecekan secara terisolasi tersebut dimungkinkan dengan adanya test doubles yang salah satu jenisnya adalah mocking. Mocking memungkinkan objek atau komponen perangkat lunak yang sebenarnya digantikan dengan objek palsu atau tiruan yang dibuat secara sederhana dan dapat dikontrol dalam pengujian. Hal tersebut membuat komponen yang diuji dapat lebih mudah untuk dilakukan pengujian. Proses pengujian tiap komponen bermanfaat untuk dapat mengidentifikasi masalah atau bug dengan lebih cepat sehingga produk yang dihasilkan memiliki kualitas yang terjamin dan sesuai dengan kebutuhan pengguna.

--

--