I am having issues properly patching an imported function in pytest. The function I want to patch is a function designed to do a large SQL fetch, so for speed I would like to replace this with reading a CSV file. Here is the code I currently have:
from data import postgres_fetch
import pytest
#pytest.fixture
def data_patch_market(monkeypatch):
test_data_path = os.path.join(os.path.dirname(__file__), 'test_data')
if os.path.exists(test_data_path):
mock_data_path = os.path.join(test_data_path, 'test_data_market.csv')
mock_data = pd.read_csv(mock_data_path)
monkeypatch.setattr(postgres_fetch, 'get_data_for_market', mock_data)
def test_mase(data_patch_market):
data = postgres_fetch.get_data_for_market(market_name=market,
market_level=market_level,
backtest_log_ids=log_ids,
connection=conn)
test_result= build_features.MASE(data)
However when I run this test I am getting a type error about calling a DataFrame:
TypeError: 'DataFrame' object is not callable
I know the csv can be read properly as I've tested that separately, so I assume something is wrong with how I am implementing the patch fixture, but I can't seem to work it out
Here, your call to monkeypatch.setattr is replacing any call to postgres_fetch.get_data_for_market with a call to mock_data.
This can't work since mock_data is not a function - its a DataFrame object.
Instead, in your call to monkeypatch.setattr, you need to pass in a function that returns the mocked data (i.e. the DataFrame object).
Hence, something like this should work:
#pytest.fixture
def data_patch_market(monkeypatch):
test_data_path = os.path.join(os.path.dirname(__file__), 'test_data')
if os.path.exists(test_data_path):
mock_data_path = os.path.join(test_data_path, 'test_data_market.csv')
mock_data = pd.read_csv(mock_data_path)
# The lines below are new - here, we define a function that will return the data we have mocked
def return_mocked(*args, **kwargs):
return mock_data
monkeypatch.setattr(postgres_fetch, 'get_data_for_market', return_mocked)
Related
I'm trying to patch a function in Pytest's pytest_sessionstart(). I was expecting the patch function to return {'SENTRY_DSN': "WRONG"}, however. I'm getting back <MagicMoc ... id='4342393248'> object in the test run.
import pytest
from unittest.mock import patch, Mock
def pytest_sessionstart(session):
"""
:type request: _pytest.python.SubRequest
:return:
"""
mock_my_func = patch('core.my_func')
mock_my_func.return_value = {'SENTRY_DSN': "WRONG"}
mock_my_func.__enter__()
def unpatch():
mock_my_func.__exit__()
This has been correctly answered by #gold_cy, so this is just an addition: as already mentioned, you are setting return_value to the patch object, not to the mock itself. The easiest way to correct is is to use instead:
from unittest.mock import patch
def pytest_sessionstart(session):
"""
:type request: _pytest.python.SubRequest
:return:
"""
mock_my_func = patch('core.my_func', return_value = {'SENTRY_DSN': "WRONG"})
mock_my_func.start()
This sets the return value to the mock without the need to create a separate Mock object.
Issue here seems to be that you are not properly configuring the Mock object. Given the code you have shown I am going under the assumption that you are calling some function in the following way:
with foobar() as fb:
# something happens with fb here
That call evaluates to to this essentially:
foobar().__enter__()
However, in the patching that you have shown, you have made a few critical mistakes.
You have not defined the return value of __enter__.
When you initialize a Mock object, it returns a brand new Mock object, therefore when you call __enter__ at the end of your function, it is returning a brand new object, not the one you originally created.
If I understand correctly you probably want something like this:
import pytest
from unittest.mock import patch, Mock
def pytest_sessionstart(session):
"""
:type request: _pytest.python.SubRequest
:return:
"""
mock_my_func = patch('core.my_func')
mock_context = Mock()
mock_context.return_value = {'SENTRY_DSN': "WRONG"}
mock_my_func.return_value.__enter__.return_value = mock_context
# this now returns `mock_context`
mock_my_func().__enter__()
Now mock_my_func().__enter__() returns mock_context which we can see works as expected when we do the following:
with mock_my_func() as mf:
print(mf())
>> {'SENTRY_DSN': 'WRONG'}
I have function, that I want test in pytest. In this function I make call to DB, nothing important. I want to mock this and after that mock count().
from app import mongo
img = mongo.db.images.find({"url": data, }).sort("date", -1).limit(1)
if img.count() > 0:
....
Is it possible? I tried with this code but not working.
#patch('app.main.mongo')
def test_images_(mock_requests):
images_all_data = []
mock_requests.db.images.find.return_value = images_all_data
I asked the same question in GitHub.
I learned about pytest-helpers-namespace from s0undt3ch in his very helpful answer. However I found a usecase I cant seem to find an obvious workaround. Here is the paste of my original question on GitHub.
How can I use the fixtures already declared in my conftest within my helper functions?
I am have a large, memory heavy configuration object (for simplicity, a dictionary) in all test, but I dont want to tear it down and rebuild this object, thus scoped as session and reused. Often times, I want to grab values from the configuration object within my test.
I know reusing fixtures within fixtures, you have to pass a reference
# fixtures
#pytest.fixture(scope="session")
def return_dictionary():
return {
"test_key": "test_value"
}
#pytest.fixture(scope="session")
def add_random(return_dictionary):
_temp = return_dictionary
_temp["test_key_random"] = "test_random_value"
return _temp
Is it because pytest collects similar decorators, and analyzes them together? I would like someone's input into this. Thanks!
Here is a few files I created to demonstrate what I was looking for, and what the error I am seeing.
# conftest.py
import pytest
from pprint import pprint
pytest_plugins = ["helpers_namespace"]
# fixtures
#pytest.fixture(scope="session")
def return_dictionary():
return {
"test_key": "test_value"
}
# helpers
#pytest.helpers.register
def super_print(_dict):
pprint(_dict)
#pytest.helpers.register
def super_print_always(key, _dict=return_dictionary):
pprint(_dict[key])
# test_check.py
import pytest
def test_option_1(return_dictionary):
print(return_dictionary)
def test_option_2(return_dictionary):
return_dictionary["test_key_2"] = "test_value_2"
pytest.helpers.super_print(return_dictionary)
def test_option_3():
pytest.helpers.super_print_always('test_key')
key = 'test_key', _dict = <function return_dictionary at 0x039B6C48>
#pytest.helpers.register
def super_print_always(key, _dict=return_dictionary):
> pprint(_dict[key])
E TypeError: 'function' object is not subscriptable
conftest.py:30: TypeError
#pytest.fixture(scope="function",
params=load_json("path_to_json.json"))
def valid_data(self, request):
return request.param
So thats one fixture in one of my test class. They contain my expected test data. Before each test, i need to modify those json file.
#pytest.fixture(scope="session", autouse=True)
def prepare_file():
// Doing the change and writing it to the json file
But when i run the test, it seem the file are not getting update. But when the test finish. They are updated. What is happening ?
Some things you should understand:
Your fixture scopes definitely need to match if you want to use one inside of the other
Your individual fixtures can access other fixtures if you pass them along
I am not entirely sure if this solves your question, but:
import json
#pytest.fixture(scope="function"):
def output_json_filepath():
return 'path/to/file'
#pytest.fixture(scope="function"):
def json_data(request):
return request.param
#pytest.fixture(scope="function"):
def prepared_data(json_data):
# do something here?
return prepared_data
# Not sure why you need this...
#pytest.fixture(scope="function"):
def dump_data(prepared_data, output_json_filepath):
with io.BytesIO(output_json_filepath, 'wb') as stream:
stream.write(prepared_data)
...
#pytest.mark.unit_test
def some_test(prepared_data):
# use your prepared_data here in your test.
I am in the process of learning unit testing, however I am struggling to understand how to mock functions for unit testing. I have reviewed many how-to's and examples but the concept is not transferring enough for me to use it on my code. I am hoping getting this to work on a actual code example I have will help.
In this case I am trying to mock isTokenValid.
Here is example code of what I want to mock.
<in library file>
import xmlrpc.client as xmlrpclib
class Library(object):
def function:
#...
AuthURL = 'https://example.com/xmlrpc/Auth'
auth_server = xmlrpclib.ServerProxy(AuthURL)
socket.setdefaulttimeout(20)
try:
if pull == 0:
valid = auth_server.isTokenValid(token)
#...
in my unit test file I have
import library
class Tester(unittest.TestCase):
#patch('library.xmlrpclib.ServerProxy')
def test_xmlrpclib(self, fake_xmlrpclib):
assert 'something'
How would I mock the code listed in 'function'? Token can be any number as a string and valid would be a int(1)
First of all, you can and should mock xmlrpc.client.ServerProxy; your library imports xmlrpc.client as a new name, but it is still the same module object so both xmlrpclib.ServerProxy in your library and xmlrpc.client.ServerProxy lead to the same object.
Next, look at how the object is used, and look for calls, the (..) syntax. Your library uses the server proxy like this:
# a call to create an instance
auth_server = xmlrpclib.ServerProxy(AuthURL)
# on the instance, a call to another method
valid = auth_server.isTokenValid(token)
So there is a chain here, where the mock is called, and the return value is then used to find another attribute that is also called. When mocking, you need to look for that same chain; use the Mock.return_value attribute for this. By default a new mock instance is returned when you call a mock, but you can also set test values.
So to test your code, you'd want to influence what auth_server.isTokenValid(token) returns, and test if your code works correctly. You may also want to assert that the correct URL is passed to the ServerProxy instance.
Create separate tests for different outcomes. Perhaps the token is valid in one case, not valid in another, and you'd want to test both cases:
class Tester(unittest.TestCase):
#patch('xmlrpc.client.ServerProxy')
def test_valid_token(self, mock_serverproxy):
# the ServerProxy(AuthURL) return value
mock_auth_server = mock_serverproxy.return_value
# configure a response for a valid token
mock_auth_server.isTokenValid.return_value = 1
# now run your library code
return_value = library.Library().function()
# and make test assertions
# about the server proxy
mock_serverproxy.assert_called_with('some_url')
# and about the auth_server.isTokenValid call
mock_auth_server.isTokenValid.assert_called_once()
# and if the result of the function is expected
self.assertEqual(return_value, 'expected return value')
#patch('xmlrpc.client.ServerProxy')
def test_invalid_token(self, mock_serverproxy):
# the ServerProxy(AuthURL) return value
mock_auth_server = mock_serverproxy.return_value
# configure a response; now testing for an invalid token instead
mock_auth_server.isTokenValid.return_value = 0
# now run your library code
return_value = library.Library().function()
# and make test assertions
# about the server proxy
mock_serverproxy.assert_called_with('some_url')
# and about the auth_server.isTokenValid call
mock_auth_server.isTokenValid.assert_called_once()
# and if the result of the function is expected
self.assertEqual(return_value, 'expected return value')
There are many mock attributes to use, and you can change your patch decorator usage a little as follows:
class Tester(unittest.TestCase):
def test_xmlrpclib(self):
with patch('library.xmlrpclib.ServerProxy.isTokenValid') as isTokenValid:
self.assertEqual(isTokenValid.call_count, 0)
# your test code calling xmlrpclib
self.assertEqual(isTokenValid.call_count, 1)
token = isTokenValid.call_args[0] # assume this token is valid
self.assertEqual(isTokenValid.return_value, 1)
You can adjust the code above to satisfy your requirements.