Let’s consider how python standard unittest module suppose to use mocks.
Assume we want to test a method that creates and uses objects of other classes:
|
|
Here is how we can test object creation (Snippet is taken from official documentation):
|
|
I would say that in our case we need also to check that each object (object1
and object2
) were instanciated and called with correct parameters. Then we can use assert_called_once_with
like this (example from official documentation):
|
|
Combining things together:
|
|
Seems good but a little bit too verbose. Assertions on calls could be eliminated. I would like to do it like in Java with Mockito:
|
|
That means you don’t need to verify intermediate calls. If they fail, the consequent calls fail as well.
The test would look like this:
|
|
You don’t need calls assertions in the end. If for example object1.run(parameter1)
returns something else, then condition of the second mock object is not met and the test fails.
Question: is there a way to achieve that?
There is a port of Mockito to Python: mockito-python There you can do virtually the same as in Java:
from mockito import when, mock, unstub
when(os.path).exists('/foo').thenReturn(True)
# or:
import requests # the famous library
# you actually want to return a Response-like obj, we'll fake it
response = mock({'status_code': 200, 'text': 'Ok'})
when(requests).get(...).thenReturn(response)
# use it
requests.get('http://google.com/')
# clean up
unstub()
But:
clean up is required
the module seems not integrating standard unittest’s mocks . Maybe I am wrong, more investigation is required. I want to use
mock.patch
and havewhen-thenReturn
construction working for it.
Similar articles
- Python Mocking 101: Fake It Before You Make It
- Using the Python mock library to fake regular functions during tests
Mocking requests in python
There is a library requests-mock that helps testing network interactions.
Installation is easy: pip install requests_mock
.
Usage example
Lets imagine you have a function get_data
that queries an external API.
We want to check that the function throws an exception if page is not found (http code 404).
|
|
m.post
mocks requests for the predefined url and returns status_code=404 and some text message. One can also specify returned JSON data.
How to avoid mocking
Python is very too flexible with respect to types. Sometimes it plays agains the developers. If interface of the mocked class A
changes you don’t notice that tests for a dependent class B
are failing if you mock A
in test_B
.
Possible way to avoid it is to pass a factory to the dependent class.
|
|
Let’s test:
|
|
Also you need to test the factory now. And it will require class monkey-patching. But in this case you put all monkey patching in one place.
Matching attributes for mocks
Keep in mind that standard mocks don’t care about non-existent atttributes. I use
|
|
instead of
|
|
That adds automatic checking of non-existent attributes and catches more errors.
Let’s see where autospec=True
can save your life.
|
|
here comes a test:
|
|
At some point you decide to change class A
. New version:
|
|
You change tests for A
but you can forget to fix B
and tests for B
. test_bar
will still work because mocked A
still have foo
. Probably you can catch that error on integration tests level, but it is better to “fail fast”.
If you use @mock.patch('module.A', autospec=True)
, then you get an error (about non-existent attribute) after you change A
on
|
|
I think autospec=True
has to be default behaviour of patch
.
See also Mocking Objects in Python section “Danger: mocking non-existent attributes”
Alternative mocking libraries
Flexmock – extended/rebuilt clone of mocking library from Ruby. Abandoned project (last update in github in 2016).
Interesting syntax. Probably cleaner mocking sometimes. Example from docs for overriding new instances of a class:
|
|
// Will it really work?
Mocking os.environ
@mock.patch.dict(os.environ, {'YOUR_VARIABLE': 'MOCKED_VALUE'})
def test_funciton():
function_using_os_environment()
See also about testing in python
- Run docker as pytest fixture
- Testing json responses in Flask REST apps with pytest
- Refactoring python code. Extracting variables and other.
- Alex Marandon. Python Mock Gotchas
- José R.C. Cruz. Using Mocks in Python. May 22, 2014
- https://semaphoreci.com/community/tutorials/testing-python-requests-with-betamax
More about mocking and testing
- Martin Fowler. Mocks Aren’t Stubs