Integration tests in Django

Unlike unit testing, where we test individual components, Integration testing is a more extensive test that targets entire applications. It combines different sections of code functionality to make sure that they behave correctly. So, we use unit testing to test individual components; then, we use integration testing to test the integration of these components.

What is Automation testing?

It is a standard software testing technique that helps you compare the actual outcome with the expected result. In Django, automation testing can be achieved through writing test scripts.

What are the benefits of creating automated tests?

  • Once tests have been written, they are run constantly to check the quality of functionality while the application continues to develop. In addition, it is not just as we write code we test. We can also test when we refactor code or modify the existing code.
  • Test to ensure that the new changes added have not affected the function of the application. Thus, in the newer versions of code, the codebase may change.- Your application may consist of third-party modules or packages, and also applications such as Django are in continuous development. Thus automated testing is handy in testing your application with new and updated packages you have installed.

What is Integration Testing in Django?

These kinds of tests test multiple pieces together to ensure that they work well with one another. Examples include the request and response cycle in Django, where we can use the Django test client.

Integration tests are larger tests focussing on user behavior and usually test the entire application. It can be represented by combining the different code functionality pieces to make sure they behave correctly.

On the other hand, integration tests are sometimes necessary even if you have coverage with unit tests since they can assist catch code regressions.

These tests often cover various aspects of the application that work together to achieve the desired result. They maintain proper data flow and frequently deal with many user interactions.

The strategy and what you select to test are the key differences between unit tests and integration tests, not the testing tools. It’s also highly typical to mix and match these two sorts of tests across your test suite as needed.

In general, tests provide one of three outcomes:

  • success – here results are usually expected,
  • failure – has unexpected results, or
  • error

You must test not only for predicted outcomes but also for how effectively your code handles unexpected outcomes.

How do Integration tests work?

Integration tests combine different parts of code and functionality to simulate user behavior. While a unit test may be used to ensure that the homepage produces an HTTP status code of 200, an integration test may be used to simulate a user’s whole registration process.

Integration testing examines how the many components of your system interact, such as URL routing, logic in views, logging, and querying your models.

Integration tests can easily be confused with functional testing, a type of black-box testing that verifies that a specific piece of functionality (typically documented) performs as expected.

Despite various tests in a Django project, view tests could be already called integration tests because they may include dealing with models, logging, etc. On the other hand, UI tests deal with high-level tests classified as functional, integration, or system tests. For example, we’d use selenium and Django selenium to create some in-browser tests that run through pre-defined scenarios like login. Then, we’d do something and finally log out. Further, we can log in with invalid credentials and then see what error appears and so on. These tests would be called functional, system, integration, or UI tests.

The best practices for writing tests in Django

It should be tested if it has the potential to break. Models, views, forms, templates, validators, and so on are all examples of these testable units.

In general, each test should only cover one function.

Maintain a straightforward approach. You don’t want to be tasked with writing tests on top of other tests. In fact, before pushing code to production, run tests every time code is PULLED or PUSHED from the repo and in the staging environment.

When upgrading to a newer version of Django locally, run your test suite, correct errors, PUSH to the repo and staging and test the code again in staging before releasing it.

How to structure your tests

Organize your tests to match your project’s needs. We prefer to put all of the tests for each project in the tests.py file or organize them by what we are testing, such as models, views, forms, etc.

You can also skip or remove the tests.py file entirely and organize your tests within each app as follows:

how to organize tests in Django
how to organize tests in Django

Finally, you might establish a separate test folder with a tests.py file in each app folder, mirroring the Project structure.

One of the latest creations should be used for larger projects. It’s better to adopt one of the two latter structures if you know your smaller project will someday develop into something much more significant. We prefer the first and third structures because it is easier to build tests for each app when they are all displayed in one script.

Third-party packages useful in testing

To help you write and run your test suite, use the following packages and libraries:

django-webtest

django-webtest makes writing functional tests and assertions that match the end-user experience much more accessible. In addition, you can combine these tests with Selenium tests to ensure that all templates and views are covered.

Coverage

Coverage is the metric used to assess the effectiveness of tests by displaying the percentage of your codebase covered by them. If you’re just getting started with unit testing, coverage can help you determine what should be tested. Coverage can also turn testing into a game: for example, I attempt to improve the percentage of code covered by tests each day.

Django-discover-runner

If you structure tests differently, Django-discover-runner can help you find them. For instance, if outside of tests.py. As a result, if you divide your tests into different files, as shown above, you can use discover-runner to find them.

Factory boy, model mommy

FactoryBoy, model mommy, and mock provide needed data for testing instead of fixtures or the ORM. Fixtures and the ORM are both slow, and they must be updated every time your model changes.

Creating Fake Data

There may be times when your APIs need you to merely fetch data and then store it to the appropriate database after calculation. Even if the test passes, it may fail in an unexpected way here.

What may it be? When the test database is built, it is empty. Therefore any attempt to retrieve data from it will result in an empty queryset. If you tried computing on an empty queryset, you’d get a DoesNotExist error or an Internal Server Error, or it’ll pass and return 200 Successful. However, that is dependent on your API structure.

So, how do we get the information we need? FactoryBoy is a great library that can be used to simulate data with random values. Your model class should be represented in the FactoryBoy class with exact relationships and datatypes. Why? Because FactoryBoy generates a random set of values for our model and saves them to our database using the Django ORM.

In the UnitTest module, create a file named ‘factory.py’ and write the following code.

import factory

Class StudentFactory(factory.django.DjangoModelFactory):
firstname = factory.Faker("firstname")
lastname = factory.Faker("lastname")
email = factory.Faker("email")


class Meta:
   model = Student
   django_get_or_create = ('email',)

django_get_or_create takes a unique characteristic from your model and uses it to create a database-level record. The Faker() arguments specify the type of value you want. The Factory is an excellent place to start. For more information, you can check out the factory.faker documentation.

What you need to know about Integration Tests

In a typical integration test, a large number of methods are tested (and probably needs lots of setup data or fixtures too). The use of the Django test client is a dead giveaway for an integration test in Django. For example,

from django.test.client import Client

class TestCase(SimpleTestCase):
  def test_myview(self):
    c = Client()
    response = c.get(reverse('my_view'))

Because a lot might happen in this view, you might end up testing a large amount of code rather than a tiny unit. Furthermore, because the test client is being utilized, you’re evaluating the Django framework’s mechanics that consists of URL routing, request middleware, the view itself, and the response middleware.

Beyond creating the unit tests, one of the first issues developers run into with a slow test suite is that they haven’t created enough unit tests. The majority of the test suite comprises significant integration tests that repeatedly cover the same functionality.

Avoid the database

Django includes several well-documented features for using test fixtures and ensuring that the database is cleaned after each test run. When you use unittest instead of this, you must clean up your mess and confirm that your database structure was appropriately pulled down in TestCase.

However, we believe that the ends justify the means because using fixtures is a quick way to slow tests. Because database writes are inherently expensive, Django’s fixture mechanism will reload fixtures for each test in the test case. If you’re running tests against a large data set and have a lot of them, that’s a lot of slow database writes.

When possible, use non-persistent data instead of fixtures in your tests. If you’re evaluating a method that operates on a Model instance, for example:

import unittest

class SlowerTestCase(unittest.TestCase):
  def test_model_foo(self):
    instance = MyModel.objects.create(name="Test Instance")
    self.assertTrue(model_foo(instance))

class FasterTestCase(unittest.TestCase):
  
  def test_model_foo(self):
    instance = MyModel(name="Test Instance")
    self.assertTrue(model_foo(instance))

The change is slight, but omitting the built-in database save in Manager.create() speeds up your test significantly. Of course, when your tests rely on a large number of model instances, the effect is magnified.

Finally, testing in an in-memory database like SQLite will not immediately improve the pace of your tests, a Django developer can save time by doing so. You may easily accomplish this by adding the following to the configuration file for your agile software testing environment:

if 'test' in sys.argv:
  DATABASES['default'] = {'ENGINE': 'django.db.backends.sqlite3'}

It may not be an option if you have a unique need to run tests against a specific database backend. However, for most projects that don’t deviate too far from standard ORM procedures, this can provide a significant performance boost to a database-intensive test suite.

How to make efficient use of the setUp/tearDown procedures

So, fixtures can be problematic and are sometimes referred to as a testing anti-pattern. They don’t change in response to changes in your database schema. Furthermore, there is no straightforward way to use them on a per-test basis, so you end up importing the exact fixture data multiple times when you only need it once. Even so, you’ll need to mimic a lot of relational data to test your project thoroughly.

So, what’s next?

When most developers realize this, the first thing they do is start using TestCases’ setUp and tearDown methods.

These well-documented and frequently-used methods are helpful to have on hand. That is because they are run before and after each test in your TestCase. They do, however, contain a large number of processes that are repeated throughout several tests. Always be wary of pricey operations when using setUp.

@classmethod
def setUpClass(cls):
  
  …

For example:

class TestViews(TestCase):
  
  def setUp(self) -> None:
        self.client = Client()
        self.list_url = reverse('student-list')
        self.detail_url = reverse('student-detail', args=['first project'])

        self.my_project = Project.objects.create(
            name='first project',
            cost=235
        )
@classmethod
def tearDownClass(cls):
  
  …

REST API testing

For REST API testing, also known as integration testing, we’ll utilize TestCase. APIs require database integration for CRUD activities, which, according to our definition of integration tests, deal with more than one individual component. We assume the reader already knows how to develop APIs and connect them as views in url.py in one way or another. If not, kindly check on our other introductory articles to developing APIs in Django.

from django.urls import reverse

class StudentTesting(TestCase):
  def test_getAddedStudent(self):
    payload = {"firstname": "brown", "lastname": "green","age": 34,"email":"greenbrown@stud.com"}
    
    Header = {'HTTP_AUTHORIZATION': 'auth_token'}
    response = Client().post(reverse('StudentAPI'), data=json.dumps(payload)),  content_type ='application/json', **header)
    getStudent = student.objects.filter(firstname="brown")
    self.assertNotNone(getStudent)

If you don’t utilize the ‘name’ property in your urlpatterns, ‘reverse’ won’t be able to identify the API view and will fail with a ‘no module exists’ error when you run the test.

What is the Client’s role?

It sends a request to the linked URL, which is a post request in our case.

The Client can be given any additional information that the API requires, such as authentication, bypassing it as one of the header variables.

Client().post()

If you want to supply some data related to the URL, you can do so using the parameters defined below.

Let’s consider the following URL

url: app/student/(?P[0-9]+$)

Subsequently, we can make the Client().post() as follows:

response = Client().post(reverse('StudentAPI',kwargs={'student_id':110}),
data=json.dumps(payload)),content_type ='application/json', **header)

Conclusion

Unit testing is at the heart of the Django application development experience, which may be a joy or a pain, depending on your perspective. It’s such an essential component of the Django project’s ethos that it even gets its chapter in the Django documentation.

Using TestCase for integration testing and SimpleTestCase for unit testing is an excellent method. SimpleTestCase is used when you need to evaluate your reasoning and see whether it works. It’s much easier to use than the TestCase module because it doesn’t involve creating or tearing down a test database. It assumes that a test database is only created and destroyed for the test cases requiring it. As a result, the tests will run more smoothly.

We can utilize TestCase for unit testing and disable the test database configuration if we need integration testing.

Though Django is great at unit testing, the integration testing story isn’t as straightforward. Instead, Django compensates by having finite tests for URLs, Views, and Models.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *