Is it okay with such a patch? (Memo about mock ~ Part 2 ~)

I'm not okay. It's a problem.

When I used the mock I wrote in Previous article, I got hooked on about 2 points.

It is necessary to change the patch target depending on the import method

Consider the case of testing a module that uses an external library as follows. (In the following example, http library requests is used as an external library. Some points of the following description depend on requests. The contents are as follows.) To test this module alone, you can mock the classes in the external library.

python:Module to be tested(A.target.Let it be py)


from requests import Session

#Use the Session class somewhere
s = Session()

Here, if you write as follows, you cannot mock the external library well.

External library mock(error)


import A

from mock import patch

# A.Mocking the Session class used in the target(I'm going)
with patch('requests.Session'):
	#test

The point is how to import the external library in the module under test.

If from requests import Session is specified, the Session class will be placed under the module space where the import was executed. If the module under test is ʻA.target.py, it will be ʻA.target.Session.

The module to be tested uses the Session class (ʻA.target.Session`) imported under it, so you have to mock this.

External library mock(correct)


import A

from mock import patch

# A.Mocking the Session class imported in target
with patch('A.target.Session'):
	#test

On the other hand, what if you say ʻimport requests`?

Module to be tested(When not using from)


import requests

#Use the Session class somewhere
s = requests.Session()

This time, without importing the Session class under my control, I specify requests.Session and the package of the external library. Therefore, the mock target is also requests.Session instead of ʻA.target.Session`.

External library mock(When not using from)


import A

from mock import patch

#Mocking the Session class in the requests package
with patch('requests.Session'):
	#test

Precautions when mocking properties

With patch, the specified target attribute (method, etc.) is also automatically mocked. In this case, a Mock class called MagicMock is used by default.

Example that the attribute of the specified target is mocked


>>> import requests
>>> from mock import patch

# requests.The post method of the Session class is mocked
>>> with patch('requests.Session'):
    s = requests.Session()
    print(s.post)
...
<MagicMock name='Session().post' id='4401343792'>

Consider the case where the response mock is set as the return value of the post method of the requests.Session class that is automatically mocked above. Since the response of the post method has a'text'property, it is assumed that the same property (mock) is set in the mock response to be created.

Use PropertyMock to mock a property. Even if you use MagicMock, you cannot specify the return value of the property (= return value of getter).

Use MagicMock for properties(Failure)


>>> import requests
>>> from mock import patch, MagicMock

>>> with patch('requests.Session'):
    s = requests.Session()
    response = MagicMock()
    # 'text'Mock properties with MagicMock(try to)
    text = MagicMock()
    text.return_value = 'property'
    response.text = text
    s.post.return_value = response
    print(s.post().text)
...

# text.return_The value specified for value is not returned
<MagicMock name='Session().post().text' id='4506288536'>

However, simply replacing MagicMock with PropertyMock does not change the result.

Use PropertyMock for properties(Failure)


>>> import requests
>>> from mock import patch, MagicMock, PropertyMock

>>> with patch('requests.Session'):
    s = requests.Session()
    response = MagicMock()
    #Use PropertyMock instead of MagicMock
    text = PropertyMock()
    text.return_value = 'property'
    response.text = text
    s.post.return_value = response
    print(s.post().text)
...

#Still not working...
<PropertyMock name='Session().post().text' id='4506377240'>

This is a problem that occurs when trying to set a PropertyMock for a mock instance (the response mock in the above example). In this case, you need to set PropertyMock to the type object, not the mock object itself.

Use PropertyMock for properties(success)


>>> import requests
>>> from mock import patch, MagicMock, PropertyMock

>>> with patch('requests.Session'):
    s = requests.Session()
    response = MagicMock()
    text = PropertyMock()
    text.return_value = 'property'
    #If you set a PropertyMock to another mock, set it to that type object, not the mock object itself.
    type(response).text = text
    s.post.return_value = response
    print(s.post().text)
...
property

I've written a lot so far

Both were properly written in the documentation ...

http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch http://www.voidspace.org.uk/python/mock/mock.html?highlight=propertymock#mock.PropertyMock

Conclusion

Let's read the document properly.

Recommended Posts

Is it okay with such a patch? (Memo about mock ~ Part 2 ~)
Memo about Sphinx Part 1 (Creating a project)
A memo about building a Django (Python) application with Docker
A memo when face is detected with Python + OpenCV quickly
A memorandum about Python mock
Make a thermometer with Raspberry Pi and make it viewable with a browser Part 4
A memo for when pip3 is installed with python2.7 for some reason
[Python] What is a with statement?
Play with a turtle with turtle graphics (Part 1)
Is it a character string operation?
A note about mock (Python mock library)
When you want to use it as it is when using it with lambda memo
Learn softly about COM (Component Object Model) Part 2 Is it really language-independent?