Suppose we have the following Flask view function to test. Specifically, we want to mock create_foo() as it writes to the filesystem.
# proj_root/some_project/views.py
from some_project import APP
from some_project.libraries.foo import create_foo
#APP.route('/run', methods=['POST']
def run():
bar = create_foo()
return 'Running'
Now we want to write a unit test for run() with the call to create_foo() mocked to avoid creating unnecessary files.
# proj_root/tests/test_views.py
from some_project import some_project
#pytest.fixture
def client():
some_project.APP.config['TESTING'] = True
with some_project.APP.test_client() as client:
yield client
def test_run(client, monkeypatch):
monkeypatch.setattr('some_project.libraries.foo.create_foo', lambda: None)
response = client.post('/run')
assert b'Running' in response.data
It seems that this approach should work even with the named create_foo import. The tests all pass, however the original code of create_foo is clearly being executed as a new file in the filesystem is created each time the test suite is run. What am I missing? I suspect it has something to do with the named imports based on some related questions but I'm not sure.
The correct monkeypatch is:
monkeypatch.setattr('some_project.views.create_foo', lambda: None)
The reason for this is pretty well explained here.
Related
I'm trying to use Pytest to test Python code. The code I'm testing isn't a class but is a bunch of functions in the same file that call other functions in the same file. For example advanced_function1 calls basic_function, and advanced_function2 can also call basic_function.
When I file.basic_function = Mock() in a test it isn't scoped to just that test.
I can verify this because when I run the test (test_advanced_function1) that mocks basic_function then test_advanced_function2 does not pass. However if I only run test_advanced_function2 (meaning basic_function never gets mocked in another test) then it works.
To my knowledge the way to correct this is to use a context manager in test_advanced_function1 and mock basic_function as that context manager. I don't know how to do this.
To simplify things and more directly declare intent I've got a test that checks if the function is a Mock object or not.
myfile.py
def basic_function():
return True
def advanced_function1():
return basic_function()
def advanced_function2():
return not basic_function()
test_myfile.py
from unittest.mock import Mock
import myfile
def test_basic_function(monkeypatch):
assert myfile.basic_function() == True
def test_advanced_function1():
myfile.basic_function = Mock(return_value='foo')
assert myfile.basic_function() == 'foo'
def test_advanced_function2():
assert not isinstance(myfile.basic_function, Mock)
So, how do I use a context manager in test_advanced_function1 to mock basic_function?
EDIT: To clarify, I can't assign the mock as a fixture because there are multiple test cases that run in the real code I'm working on and I can't mock basic_function for all the asserts in advanced_function1. Yes I know I should break this massive test apart into smaller tests but I'm just learning the codebase and don't want to change too much before getting all their tests working again.
The mock.patch context manager is seldom used directly in pytest. Instead we use fixtures. The plugin pytest-mock provides a fixture for using the mock.patch API in a more "pytest-thonic" way, like this:
import pytest
import myfile
def test_basic_function():
assert myfile.basic_function() == True
def test_advanced_function1(mocker):
mocker.patch("myfile.basic_function", return_value="foo")
assert myfile.advanced_function1() == 'foo'
def test_advanced_function2():
assert myfile.advanced_function2() == False
One possible solution is to use patch in a context manager.
test_myfile.py
from unittest.mock import patch
from unittest.mock import Mock
import myfile
def test_basic_function(monkeypatch):
assert myfile.basic_function() == True
def test_advanced_function1():
with patch('myfile.basic_function') as basic_function:
basic_function.return_value = 'foo'
assert myfile.basic_function() == 'foo'
def test_advanced_function2():
assert not isinstance(myfile.basic_function, Mock)
But please let me know if there's a more elegant way to do this!
For instance, every time a test finds
database.db.session.using_bind("reader")
I want to remove the using_bind("reader")) and just work with
database.db.session
using mocker
Tried to use it like this in conftest.py
#pytest.fixture(scope='function')
def session(mocker):
mocker.patch('store.database.db.session.using_bind', return_value=_db.db.session)
But nothing has worked so far.
Code under test:
from store import database
results = database.db.session.using_bind("reader").query(database.Order.id).join(database.Shop).filter(database.Shop.deleted == False).all(),
and I get
AttributeError: 'scoped_session' object has no attribute 'using_bind' as an error.
Let's start with an MRE where the code under test uses a fake database:
from unittest.mock import Mock, patch
class Session:
def using_bind(self, bind):
raise NotImplementedError(f"Can't bind {bind}")
def query(self):
return "success!"
database = Mock()
database.db.session = Session()
def code_under_test():
return database.db.session.using_bind("reader").query()
def test():
assert code_under_test() == "success!"
Running this test fails with:
E NotImplementedError: Can't bind reader
So we want to mock session.using_bind in code_under_test so that it returns session -- that will make our test pass.
We do that using patch, like so:
#patch("test.database.db.session.using_bind")
def test(mock_bind):
mock_bind.return_value = database.db.session
assert code_under_test() == "success!"
Note that my code is in a file called test.py, so my patch call applies to the test module -- you will need to adjust this to point to the module under test in your own code.
Note also that I need to set up my mock before calling the code under test.
I am trying to get coverage up on my Python code and am testing my Flask app. Without being too specific, let's look at the sample code here:
# from /my_project/app_demo/app.py
from private_module import logs
from flask import Flask
import logging
logs.init_logging()
logger = logging.getLogger(__name__)
app = Flask(__name__)
#app.route("/greet/<name:name>", methods=["GET"])
def greet(name):
logger.info(f"{name} wants a greeting")
return f"Hello, {name}!"
if __name__ == "__main__":
app.run(debug=True)
If I am to write a unit test for the greet function, I want to mock the logger to assert it is called when it is created and when the info method is called. For this example, the sources root folder is app_demo and there is an init.py in that python folder.
# from /my_project/tests/test_app.py
import pytest
from unittest import mock
#pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
def test_greet(client):
logs_mock = mock.patch("app_demo.app.logs.init_logging")
logger_mock = mock.patch("app_demo.app.logging.getLogger")
actual = client.get("George")
assert "Hello, George!" == actual
# these next assertions do not work
logs_mock.assert_called_once_with()
logging_mock.assert_called_once_with("app_demo.app") # not sure if this will be "__main__"
logging_mock.return_value.info.assert_called_once_with("George wants a greeting")
If I debug where the logger.info is called in the greet function, the object is a logger and not the mock object I want it to be.
I have tried making the mock in another fixture as well but that did not make it work either.
Using Python's Unittest framework how do you mock or replace a module that has code that is run as the module is loaded?
I understand this is poorly written code, but this is similar to what I have to test. (See example)
I understand that once a module is imported it can be patched to use mocks. But what if there is code that is run immediately?
I have a file that I need to put under test. One of the files it imports runs code immediately, see example.
file_under_test.py
from somewhere.something.worker import a_func as f
class ClassToTest():
__init__(self):
...
the somewhere.something.worker module
import os
import redis
REDIS_HOST = os.environ.get('redishost', '') #<-- Mock this
connection = redis.Redis(host=REDIS_HOST) #<--- Mock this
class AClass():
...
def a_func():
connection.doSomething()
...
Defer creating the connection until you are really ready for it to happen. As a bonus, you can have init_connection take an optional pre-allocated connection rather than always creating it on-demand. This makes it easier to migrate towards avoiding the global connection altogether.
import os
import redis
connection = None
def init_connection(c=None):
global connection
if connection is None:
if c is None:
c = redis.Redis(host=os.environ.get('redishost', ''))
connection = c
...
Then, in your test module, you can call init_connection from inside setupModule, with the option of passing in the desired connection-like object
instead of having to patch anything.
def setupModule():
init_connection()
# or
# conn = Mock()
# ... configure the mock ...
# init_connection(conn)
class ClassToTest():
__init__(self):
...
I'm using Py.Test to test functions in a Python Flask app.
I have the tests passing fine when I use one "app_test.py" file that contains all the fixtures and tests. Now that I've split the fixtures into their own module, and separated the tests into different modules which each import the fixtures module I'm running into issues.
If I run tests on each module individually, everything passes fine:
pytest tests/test_1.py, pytest tests/test_2.py, pytest tests/test_3.py, etc. However trouble starts if I want to run all the tests in sequence with one command, e.g. pytest tests.
I get the first module of tests passing and all future ones report an error:
AssertionError: A setup function was called after the first request was handled.
This usually indicates a bug in the application where a module was not imported
and decorators or other functionality was called too late.
E To fix this make sure to import all your view modules, database models
and everything related at a central place before the application starts
serving requests.
All the tests and fixtures in one file looks something like this:
# app_test.py
from flask_app import create_app
#pytest.fixtures(scope="session")
def app(request):
app = create_app()
with app.app_context():
yield app
#pytest.fixture(scope="session"):
def client(request, app):
client = app.test_client()
return client
def test1(client):
# test body
def test2(client):
# test body
...
I run $ pytest app_test.py and everything runs perfectly.
Now let's say we split those into three different modules: fixures.py, test_1.py and test_2.py. The code now looks like this.
# tests/fixtures.py
from flask_app import create_app
#pytest.fixtures(scope="session")
def app(request):
app = create_app()
with app.app_context():
yield app
#pytest.fixture(scope="session"):
def client(request, app):
client = app.test_client()
return client
# tests/test_1.py
from tests.fixtures import app, client
def test_1(client):
# test body
# tests/test_2.py
from tests.fixtures import app, client
def test_2(client):
# test body
If we run $ pytest tests then tests/test_1.py will pass, and tests/test_2.py will raise the error.
I've looked at this gist, and have tried tagging the test functions with #pytest.mark.usefixture with no success.
How can one run Py.Test with modularized fixtures on a directory that contains multiple test files?
You use the fixtures slightly improperly.
Specifically, you declare multiple fixtures of the same name and with the same function-object, but detected at different modules. From the pytest point of view, these should be the separate functions, because pytest does dir(module) to detect the tests & fixtures in the file. But somehow due to the same function-object, I think, pytest remembers them as the same; so once you get to the second test file, it tries the locally detected fixture name, but finds it already prepared and fails.
The proper use would be to create a pseudo-plugin conftest.py, and put all the fixtures there. Once declared, these fixtures will be available to all files in that dir and all sub-dirs.
Note: such fixtures must NOT be imported into the test files. The fixtures are NOT the functions. Pytest automatically prepares them and feeds them to the tests.
# tests/conftest.py
import pytest
from flask_app import create_app
#pytest.fixtures(scope="session")
def app(request):
app = create_app()
with app.app_context():
yield app
#pytest.fixture(scope="session")
def client(request, app):
client = app.test_client()
return client
And the test files (note the absence of the imports!):
# tests/test_1.py
def test_1(client):
# test body
# tests/test_2.py
def test_2(client):
# test body
See more: https://docs.pytest.org/en/latest/writing_plugins.html
If you are using an application instance then this will work for you:
import os
import tempfile
import pytest
from my_app import app, db
#pytest.fixture
def client():
db_fd, app.config["DATABASE"] = tempfile.mkstemp()
app.config["TESTING"] = True
with app.app_context():
db.init_app(app)
yield app
os.close(db_fd)
os.unlink(app.config["DATABASE"])
#pytest.fixture
def client(request):
client = app.test_client()
return client