【问题标题】:Django: how to mock a class inside the API viewDjango:如何在 API 视图中模拟一个类
【发布时间】:2019-07-05 17:42:37
【问题描述】:

标题可能有点混乱。

假设我有一个带有post 方法的APIView。在 post 方法中,我介绍了一个有自己方法的类。在这种情况下,它是一个处理上传到 S3 的类,这是我在运行 unittest 时想跳过的。

class SomeView(APIView):
    def post(self):
        # do something here
        input1 = some_process(payload_arg1)
        input2 = some_other_process(payload_arg2)
        uploader = S3Uploader()
        s3_response = uploader.upload_with_aux_fxn(input1, input2)
        if s3_response['status_code'] == 200:
            # do something else
            return Response('Good job I did it!', status_code=200)
        else:
            return Response('noooo you're horrible!', status_code=400)

显然,实际代码具有不同的函数调用和响应。

现在我需要模拟 uploaderuploader.upload_with_aux_fxn 所以我实际上并没有调用 S3。如何模拟它?

我在我的测试脚本中尝试过

from some_place import S3Uploader
class SomeViewTestCase(TestCase): 
    def setUp(self):        
        self.client = APIClient()
        uploader_mock = S3Uploader()
        uploader_mock.upload_support_doc = MagicMock(return_value={'status_code': 200, 'message': 'asdasdad'}
        response = self.client.post(url, payload, format='multipart')

但我仍然触发了 S3 上传(因为文件显示在 S3 中)。我如何正确模拟这个?

编辑1:

我尝试修补

def setUp(self):
    self.factory = APIRequestFactory()
    self.view = ViewToTest.as_view()
    self.url = reverse('some_url')


@patch('some_place.S3Uploader', FakeUploader)
def test_uplaod(self):
    payload = {'some': 'data', 'other': 'stuff'}
    request = self.factory.post(self.url, payload, format='json')
    force_authenticate(request, user=self.user)
    response = self.view(request)

FakeUplaoder 在哪里

class FakeUplaoder(object):
    def __init__(self):
        pass
    def upload_something(self, data, arg1, arg2, arg3):
        return {'status_code': 200, 'message': 'unit test', 's3_path': 
                'unit/test/path.pdf'}

    def downlaod_something(self, s3_path):
        return {'status_code': 200, 'message': '', 'body': 'some base64 
                stuff'}

不幸的是,这没有成功。我还是打实际课

编辑 2:

我正在使用 Django 1.11 和 Python 2.7,以防人们需要这些信息

【问题讨论】:

    标签: django django-rest-framework mocking django-unittest


    【解决方案1】:

    我想正确的方法是使用 FileField 将文件保存在模型中,然后连接 Boto 以处理生产场景中的上传。
    好好看看:
    https://docs.djangoproject.com/en/2.2/ref/models/fields/#filefield

    https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html#model
    这种方法将保留 Django 的默认行为,使事情更易于使用 Django 的默认测试客户端进行测试。

    【讨论】:

    • FileField 不保存在本地吗?如果我有多个服务器来处理生产数据,会发生什么? django 可以确保所有文件都可以访问吗? (在我相信之前我遇到过这样的问题。有时无法访问保存在服务器上的数据,因为它在不同的服务器上)
    • 没有。您可以设置一个变量来有条件地加载生产环境配置。要将媒体文件上传到 S3 设置:DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'。更多详情请访问:django-storages.readthedocs.io/en/latest/backends/…
    • 但是我怎么能不经历这个呢?我想要的只是模拟一个处理 S3Upload 的类并伪造响应。它是一个独立的类,有自己的功能。它位于 django APIView 中。我只需要嘲笑它。就是这样
    • 如果没有 Django 默认行为,我实际上想不出一种可维护的方式。
    【解决方案2】:

    看看vcrpy。它记录对外部 API 的请求一次,然后在每次运行测试时重放答案。无需手动模拟任何东西。

    【讨论】:

    • 有没有办法只用单元测试来做到这一点?我不能随时添加新库。
    • 你应该坚持嘲笑。恕我直言,重新创建这种行为需要对 boto 内部进行太多修补,这是不值得的。
    【解决方案3】:

    这是我如何在 APITestCase 中模拟 S3Uploader 的示例。

    from rest_framework import status
    from unittest import mock
    from unittest.mock import MagicMock
    
    class SomeViewTestCase(APITestCase): 
    
       @mock.patch("path.to.view_file.S3Uploader")
       def test_upload(self, s3_uploader_mock):
           """Test with mocked S3Uploader"""
           concrete_uploader_mock = MagicMock(**{
               "upload_with_aux_fxn__return_value": {"status_code": 200}
           })
           s3_uploader_mock.return_value = concrete_uploader_mock
           response = self.client.post(url, payload, format='multipart')
           self.assertEqual(response.status_code, status.HTTP_200_OK)
           s3_uploader_mock.assert_called_once()
           concrete_uploader_mock.upload_with_aux_fx.assert_called_once()
    

    【讨论】:

    • def test_upload中使用之前需要定义s3_uploader_mock吗?
    • 我试过了,用实际路径更改了 mock.patch。还将 return_value 更改为我想要的值。不幸的是,它仍然击中 S3
    • 不,你不需要事先定义它,在你添加 mock.patch 装饰器之后,它应该在你的测试函数中包含一个参数,它是模拟的类/对象。听到这个模拟仍然在 S3 上运行,我有点惊讶,这表明 @mock.patch 没有针对正确的对象。需要查看更多视图代码来诊断为什么会发生这种情况。
    • 您可能还想尝试定位您从中导入 S3Uploader 的模块,例如@mock.patch("path.to.s3_module.S3Uploader"),而不是视图模块。
    【解决方案4】:

    尝试使用MagicMock,如下所示

    from unittest import mock
    from storages.backends.s3boto3 import S3Boto3Storage
    
    
    class SomeTestCase(TestCase):
      def setUp(self):
        self.factory = APIRequestFactory()
        self.view = ViewToTest.as_view()
        self.url = reverse('some_url')
    
    
      @mock.patch.object(S3Boto3Storage, '_save', MagicMock(return_value='/tmp/somefile.png'))
      def test_uplaod(self):
        payload = {'some': 'data', 'other': 'stuff'}
        request = self.factory.post(self.url, payload, format='json')
        force_authenticate(request, user=self.user)
        response = self.view(request)
    

    【讨论】:

    • 这不起作用。它仍然命中 S3 并返回 Unexpected Error: Parameter validation failed: Invalid type for parameter Body
    • 这个错误可能是因为我使用的是json而不是multipart,但重点是不要打S3,所以我根本不应该得到这个错误
    猜你喜欢
    • 2021-07-12
    • 2016-06-26
    • 1970-01-01
    • 2017-03-08
    • 2020-10-05
    • 1970-01-01
    • 2013-03-17
    • 2017-10-30
    • 1970-01-01
    相关资源
    最近更新 更多