TypeError: Object of type Mock is not JSON serializable

I have the following test file in my code:

# Copyright 2017-2021 The Wazo Authors  (see the AUTHORS file)
# SPDX-License-Identifier: GPL-3.0-or-later

import json
from hamcrest import assert_that, equal_to, has_entries
from mock import ANY, Mock, patch, sentinel as s
from wazo_auth.config import _DEFAULT_CONFIG
from wazo_auth.tests.test_http import HTTPAppTestCase

ADDRESS_NULL = {
    'line_1': None,
    'line_2': None,
    'city': None,
    'state': None,
    'country': None,
    'zip_code': None,
}
ADDRESS_EMPTY = {
    'line_1': '',
    'line_2': '',
    'city': '',
    'state': '',
    'country': '',
    'zip_code': '',
}


class TestTenantPost(HTTPAppTestCase):

    url = '/0.1/tenants'

    def setUp(self):
        config = dict(_DEFAULT_CONFIG)
        config['enabled_http_plugins']['tenants'] = True
        super().setUp(config)

    @patch('wazo_auth.plugins.http.tenants.http.TenantDetector')
    def test_invalid_posts(self, TenantDetector):
        TenantDetector.autodetect.return_value = Mock(uuid=s.tenant_uuid)

        invalid_datas = [
            ('name', {'name': 42, 'slug': 'abc'}),
            ('name', {'name': 100 * 'foobar', 'slug': 'abc'}),
            ('slug', {'slug': 'a-b'}),
            ('slug', {'slug': 'a b'}),
            ('slug', {'slug': False}),
            ('slug', {'slug': 0}),
        ]

        for field, invalid_data in invalid_datas:
            result = self.post(invalid_data)
            assert_that(result.status_code, equal_to(400), invalid_data)
            assert_that(
                result.json,
                has_entries(
                    error_id='invalid-data',
                    message=ANY,
                    resource='tenants',
                    details=has_entries(
                        field,
                        has_entries(constraint_id=ANY, constraint=ANY, message=ANY),
                    ),
                ),
                invalid_data,
            )

    @patch('wazo_auth.plugins.http.tenants.http.TenantDetector')
    def test_empty_tenant_addresses(self, TenantDetector):
        TenantDetector.autodetect.return_value = Mock(uuid=s.tenant_uuid)

        invalid_bodies = [
            ('address', {'address': dict(
                line_1='',
                line_2='',
                city='',
                state='',
                zip_code='',
                country='',
            )}),
            ('address', {'address': dict(
                line_1=None,
                line_2=None,
                city=None,
                state=None,
                zip_code=None,
                country=None,
            )}),
        ]

        for field, invalid_body in invalid_bodies:
            result = self.post(invalid_body)
            assert_that(result.status_code, equal_to(400), invalid_body)
            assert_that(
                result.json,
                has_entries(
                    error_id='invalid-data',
                    message='Invalid tenant address length',
                    resource='tenants',
                    details=has_entries(
                        field,
                        has_entries(
                            constraint_id='length', constraint=ANY, message=ANY
                        ),
                    ),
                ),
                invalid_body,
            )

    @patch('wazo_auth.plugins.http.tenants.http.TenantDetector')
    def test_that_validated_args_are_passed_to_the_service(self, TenantDetector):
        TenantDetector.autodetect.return_value = Mock(uuid=s.tenant_uuid)

        body = {'name': 'foobar', 'slug': 'slug', 'ignored': True}
        self.tenant_service.new.return_value = {
            'name': 'foobar',
            'uuid': '022035fe-f5e5-4c16-bd5f-8fea8f4c9d08',
        }

        result = self.post(body)

        assert_that(result.status_code, equal_to(200))
        assert_that(result.json, equal_to(self.tenant_service.new.return_value))
        self.tenant_service.new.assert_called_once_with(
            uuid=None,
            name='foobar',
            slug='slug',
            phone=None,
            contact_uuid=None,
            parent_uuid=s.tenant_uuid,
            address=dict(
                line_1=None,
                line_2=None,
                city=None,
                state=None,
                zip_code=None,
                country=None,
            ),
        )

    def post(self, data):
        return self.app.post(self.url, data=json.dumps(data), headers=self.headers)

When i defined the test function above test_empty_tenant_addresses, I get the following failure:


=================================== FAILURES ===================================
__________________ TestTenantPost.test_empty_tenant_addresses __________________

self = <wazo_auth.plugins.http.tenants.tests.test_tenants.TestTenantPost testMethod=test_empty_tenant_addresses>
TenantDetector = <MagicMock name='TenantDetector' id='140628422384888'>

    @patch('wazo_auth.plugins.http.tenants.http.TenantDetector')
    def test_empty_tenant_addresses(self, TenantDetector):
        TenantDetector.autodetect.return_value = Mock(uuid=s.tenant_uuid)
    
        invalid_bodies = [
            ('address', {'address': dict(
                line_1='',
                line_2='',
                city='',
                state='',
                zip_code='',
                country='',
            )}),
            ('address', {'address': dict(
                line_1=None,
                line_2=None,
                city=None,
                state=None,
                zip_code=None,
                country=None,
            )}),
        ]
    
        for field, invalid_body in invalid_bodies:
            result = self.post(invalid_body)
>           assert_that(result.status_code, equal_to(400), invalid_body)
E           AssertionError: {'address': {'line_1': '', 'line_2': '', 'city': '', 'state': '', 'zip_code': '', 'country': ''}}
E           Expected: <400>
E                but: was <500>

wazo_auth/plugins/http/tenants/tests/test_tenants.py:92: AssertionError
------------------------------ Captured log call -------------------------------
ERROR    flask.app:app.py:1761 Exception on /0.1/tenants [POST]
Traceback (most recent call last):
  File "/root/wazo-auth/.tox/py37/lib/python3.7/site-packages/flask/app.py", line 1813, in full_dispatch_request
    rv = self.dispatch_request()
  File "/root/wazo-auth/.tox/py37/lib/python3.7/site-packages/flask/app.py", line 1799, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/root/wazo-auth/.tox/py37/lib/python3.7/site-packages/flask_restful/__init__.py", line 462, in wrapper
    return self.make_response(data, code, headers=headers)
  File "/root/wazo-auth/.tox/py37/lib/python3.7/site-packages/flask_restful/__init__.py", line 491, in make_response
    resp = self.representations[mediatype](data, *args, **kwargs)
  File "/root/wazo-auth/.tox/py37/lib/python3.7/site-packages/flask_restful/representations/json.py", line 21, in output_json
    dumped = dumps(data, **settings) + "\n"
  File "/usr/lib/python3.7/json/__init__.py", line 231, in dumps
    return _default_encoder.encode(obj)
  File "/usr/lib/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/usr/lib/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Mock is not JSON serializable


Apparently, the failure is caused by this line in my added function:

>           assert_that(result.status_code, equal_to(400), invalid_body)

which gives the following error message:

  File "/usr/lib/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Mock is not JSON serializable

I have tried some solutions that I found by searching/looking for the issue, but I could not fix the issue. Please help me fix it, thanks.

P.S: The original code is in the following github repository:

https://github.com/wazo-platform/wazo-auth


Solution 1:

For anyone facing the same issue as me, I found the solution by changing my dictionary declaration as below:

invalid_bodies = [
            ('address', {'address':
                'line_1':'',
                'line_2':'',
                'city':'',
                'state':'',
                'zip_code':'',
                'country':'',
           }),

        ]

I found the solution by logging both the input and the expected assertion output; and I noticed that the assertion failed because it was expecting an OrderedDict object instead of a dictionary.

Apparently, declaring your dictionary using the python keyworkd dict is not accepted (not JSON serializable; it converts it to OrderedDict type). Also, I removed the second body that I had (dictionary with None address fields).