【问题标题】:Mocking a Custom File Storage Backend模拟自定义文件存储后端
【发布时间】:2014-03-28 21:00:19
【问题描述】:

我创建了一个自定义文件存储后端,它使用 boto 调用 Amazon S3 并将文件存储在那里(我知道 django-storages 也可以处理这个问题,但我们遇到了几个问题)。我将它存储在 utils 模块中,并在我的模型中使用它,如下所示:

from utils.s3 import S3Storage

class Photo(models.Model):
  image = models.ImageField(storage=S3Storage(), upload_to="images")

因此,每当使用图像文件创建照片时,图像文件都会上传到 S3 存储桶。

我不想在测试期间调用 S3,但要弄清楚在这种情况下要模拟什么是很困难的。我无法模拟出整个图像字段,因为我需要通过 Tastypie 测试创建模型。

有什么想法吗?

【问题讨论】:

    标签: django unit-testing amazon-s3


    【解决方案1】:

    您可以在 S3Storage 类中模拟 _save 方法以避免上传到 S3。您可以改用FileSystemStorage

    我的解决方案是这样的:

    import mock
    from utils.s3 import S3Storage
    from django.core.files.storage import FileSystemStorage
    
    
    fss = FileSystemStorage()
    
    @mock.patch.object(S3Storage, '_save', fss._save)
    def test_something():
        assert True
    

    【讨论】:

    • 这不会写入文件系统(在测试期间您不想做的其他事情)吗?我想你不想在save()上发生任何事情
    • 是的,然后写入文件系统,但我认为这是可以接受的,因为它不是第三方API,我们最终会在运行测试后破坏测试环境。
    • 你也可以使用返回值的mock来代替真正的_save方法:@patch.object(GoogleCloudMediaStorage, '_save', MagicMock(return_value='/tmp/plain.pdf'))
    • @mock.patch.object(S3Boto3Storage, '_save', lambda self, name, content: name) 完成这项工作。
    【解决方案2】:

    我尝试了许多其他解决方案,例如在设置中覆盖DEFAULT_FILE_STORAGE 或 Manh Tai 的解决方案。一切的问题是 Django 在初始化时会将所有模型加载到内存中,这使得一旦设置模型属性就修改它有点不直观。

    用 Django 2.1 和 Python 3 测试:

    from unittest.mock import MagicMock
    from django.core.files.storage import Storage
    from django.core.files.uploadedfile import SimpleUploadedFile
    
    class CreatePhotoTest(TestCase):
        def test_post_photo(self):
            def generate_filename(filename):
                return filename
    
            def save(name, content, max_length):
                return name
    
            storage_mock = MagicMock(spec=Storage, name='StorageMock')
            storage_mock.generate_filename = generate_filename
            storage_mock.save = MagicMock(side_effect=save)
            storage_mock.url = MagicMock(name='url')
            storage_mock.url.return_value = 'http://example.com/generated_filename.png'
    
            Photo._meta.get_field('image').storage = storage_mock
    
            img = SimpleUploadedFile('file.png', b"file_content", content_type="image/png")
            data = {
                'signed_contract': img
            }
            response = self.client.post('/endpoint', data, format='multipart')
    
            self.assertTrue(storage_mock.save.called)
            generated_filename = storage_mock.save.call_args_list[0][0][0]
            uploaded_file = storage_mock.save.call_args_list[0][0][1]
            self.assertEqual(uploaded_file.name, 'file.pdf')
    

    我创建了generate_filename()save(),但如果您不想这样做,则无需这样做。这样做只是为了尽可能模拟真实 Storage 的行为并在测试中对其进行验证。

    【讨论】:

      【解决方案3】:

      这样的东西可能适用于 pytest:

      import pytest
      import os
      from django.core.files.storage import get_storage_class
      
      @pytest.fixture
      def mock_storage(monkeypatch):
          """
          Mocks the backend storage system by not actually accessing media
          """
      
          clean_name = lambda name: os.path.splitext(os.path.basename(name))[0]
      
          def _mock_save(instance, name, content):
              setattr(instance, f"mock_{clean_name(name)}_exists", True)
              return str(name).replace('\\', '/')
      
          def _mock_delete(instance, name):
              setattr(instance, f"mock_{clean_name(name)}_exists", False)
              pass
      
          def _mock_exists(instance, name):
              return getattr(instance, f"mock_{clean_name(name)}_exists", False)
      
          storage_class = get_storage_class()
      
          monkeypatch.setattr(storage_class, "_save", _mock_save)
          monkeypatch.setattr(storage_class, "delete", _mock_delete)
          monkeypatch.setattr(storage_class, "exists", _mock_exists)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-08-22
        • 2019-01-29
        • 2023-03-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多