0

i got this issue when trying to create object using Factory boy and unittest.mock for mocking payment

self = <django.db.models.sql.compiler.SQLInsertCompiler object at 0x7f5aa2913400>, field = <django.db.models.fields.CharField: card_id>
value = <MagicMock name='call_tap_api().get().resolve_expression()' id='140027256369728'>

    def prepare_value(self, field, value):
        """
        Prepare a value to be used in a query by resolving it if it is an
        expression and otherwise calling the field's get_db_prep_save().
        """
        if hasattr(value, 'resolve_expression'):
            value = value.resolve_expression(self.query, allow_joins=False, for_save=True)
            # Don't allow values containing Col expressions. They refer to
            # existing columns on a row, but in the case of insert the row
            # doesn't exist yet.
            if value.contains_column_references:
                raise ValueError(
                    'Failed to insert expression "%s" on %s. F() expressions '
>                   'can only be used to update, not to insert.' % (value, field)
                )
E               ValueError: Failed to insert expression "<MagicMock name='call_tap_api().get().resolve_expression()' id='140027256369728'>" on tap.TapSubscription.card_id. F() expressions can only be used to update, not to insert.

/usr/local/lib/python3.6/site-packages/django/db/models/sql/compiler.py:1171: ValueError
  • Hey! Without the code of the model, the factory, and the mock, it's pretty hard to get an idea of what's happening ;) If you're able to provide parts of this — maybe the part where that MagicMock is used — it would help a lot in providing a proper asnswer! – Xelnor Jul 03 '20 at 21:48

2 Answers2

0

this is the code that i was using and caused the issue

@pytest.mark.django_db
@tap_vcr.use_cassette(match_on=('method', 'path'))
@override_settings(TAP_SECRET_KEY='test')
@mock.patch('core.payment.gateway.tap.utils.initiate_payment')
def test_subscription_handler(client, user):

    user = UserFactory.create()
    with mock.patch('core.payment.gateway.tap.utils.call_tap_api') as call_tap_api:
        customer_id = create_customer(user)
        card_token = tockenize_card('123456789',
                                    '01',
                                    '01',
                                    '100',
                                    user.username)

        card_id = save_card(customer_id, card_token)
        instance = TapSubscriptionFactory.create(user=user, card_id= card_id)
        assert instance.id

create_customer() and tockenize_card() and save_card() are using call_tap_api() function that used to call payment api , with using @mock.patch the value i got was like <MagicMock name='call_tap_api().get().resolve_expression()' id='140651554367360'> i've solved the issue by :

@pytest.mark.django_db
@tap_vcr.use_cassette(match_on=('method', 'path'))
@override_settings(TAP_SECRET_KEY='test')
@mock.patch('core.payment.gateway.tap.utils.initiate_payment')
def test_subscription_handler(client, user):

    user = UserFactory.create()
    with mock.patch('core.payment.gateway.tap.utils.call_tap_api') as call_tap_api:
        customer_id = user.tap_customer_id
        card_token = factory.fuzzy.FuzzyInteger(1, 9999)

        card_id = factory.fuzzy.FuzzyInteger(1, 9999)
        instance = TapSubscriptionFactory.create(user=user, card_id= card_id)
        assert instance.id

now customer_id and card_token and card_id get a generated value instead of

<MagicMock name='call_tap_api().get().resolve_expression()' id='140651554367360'>
0

I was getting a similar error while trying to mock a Celery task delay function:

@patch('some_app.tasks.some_task.delay')
    def test_create_graph_report_mutation(self, mock_task):
        # Some code that triggers the task indirectly.
        mock_task.assert_called()

To fix this, I also mocked the function which was actually triggering the task.

@patch('some_app.models.some_function')
@patch('some_app.tasks.some_task.delay')
    def test_create_graph_report_mutation(self, mock_task, mock_some_function):
        # Some code that triggers the task indirectly.
        mock_some_function.assert_called()
        mock_task.assert_called()

Conclusion: This leads me to believe, and the solution that has been given, that if we mock a nested function and the testing code indirectly depends on it, we may either eliminate this reliance or mock the parent function as well.

mah
  • 102
  • 9