I'm having trouble mocking out the an imported module in a unit test. I'm trying to mock the PIL Image class in my module tracker.models
using the mock module. I understand you are supposed to mock things where they are used, so I've written @mock.patch('tracker.models.Image')
as my decorator for the unit test. I am trying to check whether the downloaded image gets opened as a PIL Image. The mock patch seems to be overwriting the entire Image module. Here is the error I'm getting when I run the test:
File "/home/ubuntu/workspace/tracker/models.py", line 40, in set_photo
width, height = image.size
ValueError: need more than 0 values to unpack
Here's my unit test:
test_models.py
@responses.activate
@mock.patch('tracker.models.Image')
def test_set_photo(self, mock_pil_image):
# Initialize data
hammer = Product.objects.get(name="Hammer")
fake_url = 'http://www.example.com/prod.jpeg'
fake_destination = 'Hammer.jpeg'
# Mock successful image download using sample image. (This works fine)
with open('tracker/tests/test_data/small_pic.jpeg', 'r') as pic:
sample_pic_content = pic.read()
responses.add(responses.GET, fake_url, body=sample_pic_content, status=200, content_type='image/jpeg')
# Run the actual method
hammer.set_photo(fake_url, fake_destination)
# Check that it was opened as a PIL Image
self.assertTrue(mock_pil_image.open.called,
"Failed to open the downloaded file as a PIL image.")
Here is the piece of code it is testing.
tracker/models.py
class Product(models.Model):
def set_photo(self, url, filename):
image_request_result = requests.get(url)
image_request_result.content
image = Image.open(StringIO(image_request_result.content))
# Shrink photo if needed
width, height = image.size # Unit test fails here
max_size = [MAX_IMAGE_SIZE, MAX_IMAGE_SIZE]
if width > MAX_IMAGE_SIZE or height > MAX_IMAGE_SIZE:
image.thumbnail(max_size)
image_io = StringIO()
image.save(image_io, format='JPEG')
self.photo.save(filename, ContentFile(image_io.getvalue()))
You need to configure the return value of Image.open
to include a size
attribute:
opened_image = mock_pil_image.open.return_value
opened_image.size = (42, 83)
Now when your function-under-test calls Image.open
the returned MagicMock
instance will have a size
attribute that is a tuple.
You could do the same thing for any other methods or attributes that need to return something.
The opened_image
reference is then also useful for testing other aspects of your function-under-test; you can now assert that image.thumbnail
and image.save
were called:
opened_image = mock_pil_image.open.return_value
opened_image.size = (42, 83)
# Run the actual method
hammer.set_photo(fake_url, fake_destination)
# Check that it was opened as a PIL Image
self.assertTrue(mock_pil_image.open.called,
"Failed to open the downloaded file as a PIL image.")
self.assertTrue(opened_image.thumbnail.called)
self.assertTrue(opened_image.save.called)
This lets you test very accurately if your thumbnail size logic works correctly, for example, without having to test if PIL is doing what it does; PIL is not being tested here, after all.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With