0

i'm pretty new to unittests and want to make some "simple" beginner tests in my Django application, I have a model with a "custom" save method that calls a external api if new. Can't figure out how to that that model without calling the external API, how do a implement a mock solution for this?

class Task(models.Model):
    status = models.CharField(max_length=25, choices=STATUS_CHOICES, default='new')
    ....

    def _create(self):
        ....
        return requests.post(API_URL + url, json=payload).json()

    def save(self, *args, **kwargs):
        is_new = self.created is None
        super(Task, self).save(*args, **kwargs)

        if is_new:
            self._create()

class TaskTestCase(TestCase):

    def setUp(self):

        self.task = Task.objects.create(status='new')

    def test_get_new_task(self):
        task = Task.objects.all()[0]
        self.assertEqual(task.status, 'new')
pkdkk
  • 3,905
  • 8
  • 44
  • 69

1 Answers1

3

This is a typical use case for python mock library. In your case I will probably looks like this

import mock
class TaskTestCase(TestCase):

    @mock.patch("request.post")
    def setUp(self, mocked_post):

        self.task = Task.objects.create(status='new')

    def test_get_new_task(self):
        task = Task.objects.all()[0]
        self.assertEqual(task.status, 'new')

You can use mock.patch as a decorator but also as a context manager if you want to mock a function call on a specific part of your code. See the documentation for more details.

More details

In this example we only mocked the method to avoid the called being really made. In some cases in can be a good idea to also checked that the mocked method was called, and with which arguments.

import mock
class TaskTestCase(TestCase):

@mock.patch("request.post")
def setUp(self, mocked_post):

    self.task = Task.objects.create(status='new')
    # The most basic way to check a call was performed
    mocked_post..assert_called() 

    # Checking the number of call can be a good idea
    mocked_post.assert_called_once()

    # Same idea but it also checks the arguments of the method.
    mocked_post.assert_called_once_with()

The last option is very useful to test that your are sending the right data to the external service. More details on the different options in the documentation.

Why mocking external calls is a good idea

Mocking all the external calls you can in your test suite is really a good idea. Here is a few reasons why:

  • It will improve performance and speed of your test suite. Network calls are generally time expensive, lowering the number of calls will speed up your test suite.
  • It allows you to test the data you are sending to other services. As seen with assert_called_once_with you can assert that you are sending once the proper data to other services
  • It makes your test suite more predictable. Use mocks you won't rely on any other services to test your application. From time to time external services may not respond properly (maintenance, too many requests, etc.). Using mock you will break the coupling between your test suite and other services
  • You can run tests offline (commuting, train, place, etc.).
Anto
  • 161
  • 2
  • Thanks for the great answer,. One thing, when I try out you example, i get an error due to the requests response.. is not JSON serializable .. do you have an idea how to make that work.. can i "use" some example data from the external response? – pkdkk Nov 08 '18 at 13:44
  • 1
    @pkdkk this is propably due to the fact that the mock you have setup does not return any value. You can either use `mocked_post.return_value = {}` or use `mocked_post.side_effect` to use a function to return a value – Anto Nov 08 '18 at 19:46