What's wrong with this Python mock patch?

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.