0

Given a view that creates an object from a model that has a 'unique_together' for two fields using the following code:

    def form_valid(self, form):
        field1 = form.cleaned_data['field1']
        field2 = form.cleaned_data['field2']
        try:
            TheModel.objects.create(org=self.request.org, field1=field1, field2=field2)
        except IntegrityError as e:
            if 'UNIQUE constraint' in e.message:
                messages.error(self.request, _('field1 already exists.'))
            return super(ModelFormMixin, self).form_valid(form)
        messages.success(self.request, _('Fields have been successfully updated.'))
        return super(ModelFormMixin, self).form_valid(form)

How might I unit test that the error message gets displayed when the 'unique_together' throws an error?

My current test throws a TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.

Here is the current test:

    @patch.object(messages, 'error')
    def test_error_handling(self, error_mock):

        TheModel.objects.create(org=self.org, field1='somepath', field2='anotherpath2')

        with transaction.atomic():
            response = self.client.post(reverse('configurations.create_amodel', args=(self.org.slug,)),
                {'field1': 'somepath', 'field2': 'anotherpath'}, follow=True)
            self.assertTrue(error_mock.called)

I cannot figure out how to make the test work (testing the code that is run when the exception is caught)

Here's my form class:

class RedirectForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
    self.request = kwargs.pop('request', None)
    super(RedirectForm, self).__init__(*args, **kwargs)

field1 = forms.CharField(label='field1', required=True)
field2 = forms.CharField(label='field2', required=True)

class Meta:
    model = TheModel
    fields = ('field1', 'field2')
  • 1
    This whole approach is strange. Rather than trying to create an object and catch an integrity exception, you should use a model form, which would be aware of the unique_together property and would report an error on validation – Daniel Roseman May 07 '18 at 15:40
  • I am using a modelForm (I didn't post the entire view, but a modelForm is being imported into the view to compose the form). The model form doesn't create the object - so how can it validate if the object already exists in the database? As far as I know the model form lives only on the front end, and doesn't do database validation, but maybe I'm wrong? – Sam Louis Milligan May 07 '18 at 16:15
  • That's the point, the model form *does* create the object and *does* do database validation. I don't understand what you mean about only living on the front end. – Daniel Roseman May 07 '18 at 16:20
  • Looking deeper into modelForms, it seems like there is database validation out of the box. I believe the form_valid (in the view) is the validation sequence for modelForm. Meaning - this is the normal way of doing things. Where else would you call a custom message for success and error? – Sam Louis Milligan May 07 '18 at 16:26

2 Answers2

0

You can get the messages from the response context, and then check for your errors.

messages = list(response.context['messages'])
self.assertEqual(len(messages), 1)
self.assertEqual(str(messages[0]), 'field1 already exists.')

You can read more here: How can I unit test django messages?

oxalorg
  • 2,458
  • 12
  • 27
  • I have no problem unit testing the messages. I have already unit tested the success message by mocking the success method: `@patch.object(messages, 'success')` The problem is with exceptions – Sam Louis Milligan May 07 '18 at 15:17
0

I found that the whole problem of exceptions being called within the model was averted by using "TransactionTestCase" instead of "TestCase" in the tests class:

I.E.

class CreateRedirectViewTests(TransactionTestCase):

the test for posting the error message when the unique_together exception is called then can be done with the following:

@patch.object(messages, 'error')
def test_error_message(self, error_mock):

    self.client.post(reverse('configurations.create_amodel', args=(self.org.slug,)),
            {'field1': 'somepath', 'field2': 'anotherpath'}, follow=True)

    self.client.post(reverse('configurations.create_amodel', args=(self.org.slug,)),
            {'field1': 'somepath', 'field2': 'anotherpath'}, follow=True)
    self.assertTrue(error_mock.called)