Be Independent! Start Mocking with Django!!

Be Independent! Start Mocking with Django!!

I know right, you may have experienced writing a few lines of code which depend on other codes which have been previously written. At first, everything might be absolutely fine. But when the time has came to you to write the unit tests, something bad might come to you. If you have no idea about what problem we are talking about, let me take some instances for you. For example, if you write a model in Django which have to be dependent with other model in the other app (neighbor model), you should have to avoid calling the full functional of the neighbor model to make your model can work completely. Why? It is because if the neighbor model someday will experiencing error, it means that all of your works will experiencing errors too, because they are dependent. So, to avoid this thing, we should start learning mocking!

Mocking

So, what is it? According to Arvind Padmanabhan and Narendra in https://devopedia.org/,

Mock testing is an approach to unit testing that lets you make assertions about how the code under test is interacting with other system modules.

In simple words, mocking is creating objects that simulate the behavior of real objects. The dependencies (objects which we depend to) are replaced with a fake object that can simulate the behavior of the real ones precisely. The main purpose of this activity is to isolate and focus on the code being tested and not on the behavior or state of external dependencies. To illustrate this condition, let's say, a notification service triggers an email service. As we know, we don't want to send emails every time because of the notification test. So that, we should to mock the email sending functionality.

Stubbing

So, what is it? A stub is some codes that used to stand in for some other programming functionality in software development. Stubs play an important role in software development, testing and porting, although they are also widely used in distributed computing. It may simulate the behavior of an existing piece of code, or stand in for code that has not yet been developed. A method stub merely declares itself and the parameters it accepts. Stubs are used as implementations of a known interface, where the interface is known but not the implementation.

Mock vs Stub

Although there are some similarities such as the purpose of both mock and stub is to eliminate testing all the dependencies of a class or function, there is a quite different thing between them. One of the main differences is tests that written with mocks usually follow an initialize -> set expectations -> exercise -> verify pattern to testing. While the stub would follow an initialize -> exercise -> verify.

Benefits

First of all, mocking is generally useful during unit testing so that external dependencies are no longer a constraint to the unit under test. Often those dependencies may themselves be under development. Without mocking, if a test case fails, it will be hard to know if the failure is due to our code unit or due to dependencies. Mocking therefore speeds up development and testing by failures isolation.

Moreover, mock dependencies is also very useful to avoid slow network calls or call third-party APIs. Mocking also enables product demos and evaluations. All units of a project can progress in parallel without waiting for everyone to be ready.

Last but not least, mocking is also useful to be implemented in code that have side effects should be called only in production. Examples include charging a credit card or sending a notification. Mocking is useful to validate such calls without the side effects.

Note to Know

You should remember these things before diving deep into mocking:

  1. Only mock types that you own --- External types have dependencies on their own. They might even change their behavior in a future version. Instead, create an adapter and mock that adapter.
  2. Don't mock values --- Values should not be mocked. Mocking aims to make the relationships and interactions between objects visible. Getting a value is not an interaction.
  3. Avoid mocking concrete classes --- Relationships are easier to see via interfaces rather than concrete classes. We might end up mocking some methods but forget others. Mock roles, not objects.
  4. Don't mock everything --- This is an anti-pattern. If everything is mocked, we may end up testing something quite different from production system.
  5. Use integration tests --- When testing multiple modules or if you're interested in data coming from an external database, you should do integration testing rather than mocking.
  6. Negative tests --- Use mocks to simulate errors and test error handling. Use mocks to verify that some methods/APIs are not called.

Implementation

To illustrate this, let's see one of my website project called Sistem Informasi Penilaian dan Evaluasi Praktikum, a website to help FISIP (Faculty of Social and Political Sciences) UI's students and lecturers to submit and score the practical work's (internship) reports. This project is being developed by my team, MK-PPL.

We use a library named mixer. It is developed for Django or Python, so it might be the most suitable library for our project which is also using Django as the stack. You can check that out in this link. After you check that, I will explain you how to do mocking in Django below.

Initialization

Before heading to implementation, of course we should install the library first. To make it installed in our project, type this script in your shell that is pointing to the project directory:

pip install mixer

After that, don't forget to add the that library to your requirements.txt (if you have) by typing this script:

pip freeze > requirements.txt

By doing that, mixer is already installed in your project.

Mocking and Stubbing Models

Last sprint, I was working on a feature called Detail Laporan Mahasiswa (PBI 1C). After a few days working on this feature, I realized that I am using various models from various apps to make a setUp() because the model I used depends on many other models. At first, I was thinking that I should create all object models in my test. It looked like this:

def get_image_file(self, name='test.png', ext='png', size=(50, 50), color=(256, 0, 0)):
        '''Mock image'''
        file_obj = BytesIO()
        image = Image.new("RGBA", size=size, color=color)
        image.save(file_obj, ext)
        file_obj.seek(0)
        return File(file_obj, name=name)


    def setUp(self):
        self.JWT_PAYLOAD_HANDLER = api_settings.JWT_PAYLOAD_HANDLER
        self.JWT_ENCODE_HANDLER = api_settings.JWT_ENCODE_HANDLER


        institusi = Institusi.objects.create(
            nama="testing"
        )


        tema = Tema.objects.create(
            nama="testing"
        )


        lingkup_kerja = LingkupKerja.objects.create(
            nama="testing"
        )


        self.lembaga = Lembaga.objects.create(
            nama="testing",
            jenis_pelayanan="testing",
            institusi=institusi,
            tema=tema,
            deskripsi_singkat="testing",
            praktikum_ke=1,
        )


        self.lembaga.lingkup_kerja.add(lingkup_kerja)
        self.lembaga.gambar = self.get_image_file()
        self.lembaga.save()


        self.user_mahasiswa_not_valid1 = User.objects.create_user(
            username="username0",
            role=Role.MHS.value,
            email="[email protected]",
            password="password",
            full_name="User Name0"
        )


        self.user_mahasiswa_not_valid2 = User.objects.create_user(
            username="username1",
            role=Role.MHS.value,
            email="[email protected]",
            password="password",
            full_name="User Name1"
        )


        self.user_mahasiswa = User.objects.create_user(
            username="username2",
            role=Role.MHS.value,
            email="[email protected]",
            password="password",
            full_name="User Name2"
        )


        self.user_supervisor_sekolah = User.objects.create_user(
            username="username3",
            role=Role.SSK.value,
            email="[email protected]",
            password="password",
            full_name="User Name3"
        )


        self.user_supervisor_lembaga = User.objects.create_user(
            username="username4",
            role=Role.SLB.value,
            email="[email protected]",
            password="password",
            full_name="User Name4"
        )


        supervisor_lembaga = SupervisorLembaga.objects.create(
            user=self.user_supervisor_lembaga,
            lembaga=self.lembaga,
            jabatan="jabatan"
        )


        supervisor_sekolah = SupervisorSekolah.objects.create(
            user=self.user_supervisor_sekolah,
            nip="nip"
        )


        mahasiswa_not_valid_1 = Mahasiswa.objects.create(
            user=self.user_mahasiswa_not_valid1,
            npm="npm",
            org_code="org_code",
            faculty="faculty",
            major="major",
            program="program",
            supervisor_lembaga=supervisor_lembaga,
            supervisor_sekolah=supervisor_sekolah
        )


        Mahasiswa.objects.create(
            user=self.user_mahasiswa_not_valid2,
            npm="npm",
            org_code="org_code",
            faculty="faculty",
            major="major",
            program="program",
            supervisor_lembaga=supervisor_lembaga,
            supervisor_sekolah=supervisor_sekolah
        )


        mahasiswa = Mahasiswa.objects.create(
            user=self.user_mahasiswa,
            npm="npm",
            org_code="org_code",
            faculty="faculty",
            major="major",
            program="program",
            supervisor_lembaga=supervisor_lembaga,
            supervisor_sekolah=supervisor_sekolah
        )


        praktikum = Praktikum.objects.create(
            jenis_praktikum="Praktikum 1"
        )


        MahasiswaPraktikum.objects.create(
            mahasiswa=mahasiswa,
            list_praktikum=praktikum,
            status=True
        )


        MahasiswaPraktikum.objects.create(
            mahasiswa=mahasiswa_not_valid_1,
            list_praktikum=praktikum,
            status=True
        )


        KelolaLaporanPraktikum.objects.create(
            nama_laporan='nama_laporan_1',
            mahasiswa=mahasiswa,
            jenis_praktikum=praktikum,
            waktu_deadline=datetime.datetime(
                2020,
                5,
                5,
                5,
                5,
                5
            ),
            status_publikasi=True,
            status_submisi=False,
            skor_laporan_sekolah=-1,
            skor_laporan_lembaga=-1
        )


        self.login_data_supervisor_sekolah_valid = {
            "username": "username3",
            "password": "password"
        }


Yes, it was so complicated because I should save all models that I didn't them actually. Actually I don't need any SupervisorLembaga, Lembaga, etc to be saved, but because Mahasiswa has a foreign key to SupervisorLembaga, then I should create save the SupervisorLembaga too. So, to make this more effective, I browsed for any mock and stubbing library that I can use. Then, I finally found mixer.

Because of mixer, I can replace the creation of the Lembaga, Tema, SupervisorLembaga, and many more with a single line code! The code is look like this:

supervisor_lembaga = mixer.blend(
            SupervisorLembaga, lembaga=mixer.blend(
                Lembaga, institusi=mixer.blend(
                    Institusi
                ), tema=mixer.blend(
                    Tema
                ), lingkup_kerja=mixer.blend(
                    LingkupKerja
                ), gambar=self.get_image_file()
            ), user=mixer.blend(User, role=Role.SLB.value)
        )

The code above mocks and stub all the models that previously I created but I did't actually need them. We can see, first, the code mocks and stubs SupervisorLembaga. While mocking SupervisorLembaga, I need to define the value of lembaga because it is a foreign key to Lembaga model. Knowing that, I define the value of lembaga to be another mock of model (which is Lembaga model). Lembaga model's values also need to be filled because they are mostly a foreign key. So, I also define the value of institusi, tema, and lingkup_kerja while mocking the Lembaga. I also have to mock the role value of supervisor_lembaga because it should be fixed and will be tested with expected value of Role.SLB.value.

In the other hand, the stubbing works for every variables that we don't have to initialize. In this case, Supervisor Lembaga model has 3 main fields: lembaga, user, and jabatan. The lembaga field and user field have mocked before with mixer because we have to set the expected result. But for the jabatan field, we don't have to set the expected result. I'll let the mixer does what it has to do (let it stub the field's value). Besides Supervisor Lembaga, Mixer also does this for Lembaga model, Tema model, and LingkupKerja model, because in those models, they have many fields whose values don't have to be set before.

Finally, I run the tests. Now, I get all success test cases without wasting line of codes:

That wraps up the article. If you have any suggestions, don’t hesitate to comment :)

Thanks for reading!

Ehsan Ahmadi

Senior Backend Developer | Software Engineer | Full Stack Developer | Specializing in Django Framework, Scalable Web Applications, and RESTful APIs

2 年

Thanks ??

回复

要查看或添加评论,请登录

Razaqa Dhafin Haffiyan的更多文章