2

I've been fighting with mocking a form class to replace an instance of it in a class-based view. But it looks like that because the form is in a class attribute, it happens before I replace the form class with my mock. Case in point:

app/views.py

from app.forms import SomeForm  # For some reason, this _is_ my mock...

class SomeViewClass(View):
    form = SomeForm  # ... while this is the _real_ SomeForm

    def post(self, request):
        form = self.form(request.POST, request.FILES)

        # Hacked around with pdb here
        # (Pdb) self.form = SomeForm <-- Force the mock into the object
        # (Pdb) form = self.form(request.POST, request.FILES)
        # (Pdb) form.is_valid() is now True
        # (Pdb) continue <--- Test finishes, and asserts are OK.

        if form.is_valid():  # This fails, as I'm running the real code
            # code, code, code

app/tests/test_views.py

from mock import MagicMock, patch

from django.tests import Client, TestCase


@patch('app.views.SomeForm')
    def test_post_valid_form_should_pass(self, mocked_form_class):
        """ Replacing SomeForm in SomeViewClass to pas the is_valid test
        """
        form_instance = MagicMock(spec=SomeForm())
        form_instance.is_valid.return_value = True
        mocked_form_class.return_value = form_instance

        self.client.login(**self.credentials)
        # code, code, code

As you can see in the inserted comments in app/views.py, I forcefully reloaded self.form and redefined the variable form using pdb, which made my test pass.

It seems that for some reason, the SomeViewClass is [registered,instanciated,...] before I start patching SomeForm. Any ideas on that?

Wonskcalb
  • 383
  • 2
  • 6

1 Answers1

0

The problem is that the view is already loaded by Django and the form field is already defined and pointing to the SomeForm production class.

As @DanielRoseman and @foxyblue pointed out in their comments, it is possible to patch directly a field in a class. And actually there was already an answer for that on SO. As pointed out, it is possible to use patch.object to patch a member of a class (which is, IMO, the best solution, as it's more explicit, and less prone to typos)

Test corrected:

With patch

@patch('app.views.SomeView.form', autospec=SomeForm)
    def test_post_valid_form_should_pass(self, mocked_form_class):
        """ Replacing SomeForm in SomeViewClass.form to pass the is_valid test """
        mocked_form_class.is_valid.return_value = True

        self.client.login(**self.credentials)
        # code, code, code

With patch.object

@patch.object(SomeView, 'form', autospec=SomeForm)
    def test_post_valid_form_should_pass(self, mocked_form_class):
        """ Replacing SomeForm in SomeViewClass.form to pass the is_valid test """
        mocked_form_class.is_valid.return_value = True

        self.client.login(**self.credentials)
        # code, code, code
Wonskcalb
  • 383
  • 2
  • 6