pytest to insert caplog fixture in test method - python

I have the following test class for pytest:
class TestConnection(AsyncTestCase):
'''Integration test'''
#gen_test
def test_connecting_to_server(self):
'''Connecting to the TCPserver'''
client = server = None
try:
sock, port = bind_unused_port()
with NullContext():
server = EchoServer()
server.add_socket(sock)
client = IOStream(socket.socket())
#### HERE I WANT TO HAVE THE caplog FIXTURE
with ExpectLog(app_log, '.*decode.*'):
yield client.connect(('localhost', port))
yield client.write(b'hello\n')
# yield client.read_until(b'\n')
yield gen.moment
assert False
finally:
if server is not None:
server.stop()
if client is not None:
client.close()
Within this class apparently ExpectLog is not working so after a day of digging around in pytest's documentation I found that there is this caplog fixture that you can have inserted in you methods in order to access the captured logs. It seems to work if I have a test function to which I add the caplog argument but how do I make the caplog fixture available within the methods of a test class like the one above?

Although you can't pass fixtures as parameters to unittest test methods, you can inject them as instance attributes. Example:
# spam.py
import logging
def eggs():
logging.getLogger().info('bacon')
Test for spam.eggs():
# test_spam.py
import logging
import unittest
import pytest
import spam
class SpamTest(unittest.TestCase):
#pytest.fixture(autouse=True)
def inject_fixtures(self, caplog):
self._caplog = caplog
def test_eggs(self):
with self._caplog.at_level(logging.INFO):
spam.eggs()
assert self._caplog.records[0].message == 'bacon'

Related

Python async unit tests, how to use async database connection pool?

I'm using Python 3.9.7, databases 0.4.3, asyncpg 0.24.0.
Relevant files/snippets:
tests.py
from unittest import TextTestRunner, TestSuite, TestLoader
runner = TextTestRunner()
test_loader = TestLoader()
suite = TestSuite()
test_suite = add_tests_to_suite(suite) # <- All my tests are added here
runner.run(test_suite)
db.py
from databases import Database
dal = Database() # This is the "Databases" lib instance
some_test.py
from unittest import IsolatedAsyncioTestCase
from db import dal
class SomeTest(IsolatedAsyncioTestCase):
async def some_async_test(self):
try:
await dal.connect()
# Test logic happens here
finally:
await dal.disconnect()
The code above works, however, connecting and disconnecting on every unit test is taking around 400ms, which is very slow when dealing with a large amount of unit tests. What is the proper/recommended way of dealing with async database connections in the context of unit tests?
Things I tried:
Move dal.connect() to tests.py, but that file is not in the asyncio context, therefore I cannot await the connect() function.
Create an asyncio loop in tests.py just so I can await the connect() function, but this approach throws:
RuntimeWarning: coroutine 'IsolatedAsyncioTestCase._asyncioLoopRunner' was never awaited`
Run the function dal.connect() only once, rather than on every test, but it throws:
asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress

Run pytest test suite against multiple database versions

I build an application that uses a database in the backend. For integration tests, I start the database in Docker and run a test suite with pytest.
I use a session scoped fixture with autouse=True to start the Docker container:
#pytest.fixture(scope='session', autouse=True)
def run_database():
# setup code skipped ...
# start container with docker-py
container.start()
# yield container to run tests
yield container
# stop container afterwards
container.stop()
I pass the database connection to the test functions with another session scoped fixture:
#pytest.fixture(scope='session')
def connection():
return Connection(...)
Now I can run a test function:
def test_something(connection):
result = connection.run(...)
assert result == 'abc'
However, I would like to run my test functions against multiple different versions of the database.
I could run multiple Docker containers in the run_database() fixture. How can I parametrize my test functions so that they run for two different connection() fixtures?
The answer by #Guy works!
I found another solution for the problem. It is possible to parametrize a fixture. Every test function that uses the fixture will run multiple times: https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures
Thus, I parametrized the connection() function:
#pytest.fixture(scope='session', params=['url_1', 'url_2'])
def connection(request):
yield Connection(url=request.param)
Now every test function that uses the connection fixture runs twice. The advantage is that you do not have to change/adapt/mark the existing test functions.
You can send a function that yields connections and pass it with #pytest.mark.parametrize. If you change the the scope of run_database() to class it will run for every test
def data_provider():
connections = [Connection(1), Connection(2), Connection(3)]
for connection in connections:
yield connection
#pytest.fixture(scope='class', autouse=True)
def run_database():
container.start()
yield container
container.stop()
#pytest.mark.parametrize('connection', data_provider())
#pytest.mark.testing
def test_something(connection):
result = connection.run()
assert result == 'abc'
If you add #pytest.mark.parametrize('connection', data_provider()) to run_database() the connection will be passed to there as well.

pytest: how to skip the testcases and jump right up to cleanup if something goes wrong in setup?

I understand that in pytest, the preferred way for setup and cleanup is to utilize yield, like
class TestSomething():
#pytest.fixture(scope="class", autouse=True)
def setup_cleanup(self, request):
...
yield
...
def test_something(self):
...
Problem is, if there is a failure in the setup part, before yield happens, the cleanup code would not get a chance to run.
Is it possible that, when some critical failure occurs in setup, all testcases are skipped, and the control is taken over by the cleanup (after yield in the method setup_cleanup)?
setup_cleanup triggers the test function, but it is still a function. An exception raised in any step will prevent the rest of it to be excited.
A work around will be to use try finally. It will allow the test and teardown to run without swallowing the exception
#pytest.fixture(scope="class", autouse=True)
def setup_cleanup(self, request):
try:
print('Setup')
raise Exception("Setup Exception")
yield
finally:
print('Teardown')
def test_example_test(self):
print('Test')
With the exception
Setup
Teardown
test setup failed
self = <ExampleTest.TestSomething object at 0x045E6230>
request = <SubRequest 'setup_cleanup' for <Function test_something>>
#pytest.fixture(scope="class", autouse=True)
def setup_cleanup(self, request):
print()
try:
print('Setup')
> raise Exception("Setup Exception")
E Exception: Setup Exception
And without
Setup
.Test
Teardown

How to write a unit test with mockito in Python

I'm a newbie in Python and I have problem with mockito in Python.
My production code looks like below:
from stompest.config import StompConfig
from stompest.sync import Stomp
class Connector:
def sendMessage(self):
message = {'message'}
dest = '/queue/foo'
def _send(self, message='', dest=''):
config = StompConfig(uri="tcp://localhost:61613")
client = Stomp(config)
client.connect()
client.send(body=message, destination=dest,
headers='')
client.disconnect()
as you see I would like to send a message using Stomp protocol. I my test I would like to test that w when I invoke a send method from Connector class a send method from Stompest library will be only once invoked.
My unit test looks like:
from Connector import Connector
import unittest
from mockito import *
import stompest
from stompest.config import StompConfig
from stompest.sync import Stomp
class test_Connector(unittest.TestCase):
def test_shouldInvokeConnectMethod(self):
stomp_config = StompConfig(uri="tcp://localhost:61613")
mock_stomp = mock(Stomp(stomp_config))
connector = Connector()
connector.sendMessage()
verify(mock_stomp, times=1).connect()
When I run test in debug mode I see that method for instance connect() is invoked and method send as well, but as a result of test I get:
Failure
Traceback (most recent call last):
File "C:\development\systemtest_repo\robot_libraries\test_Connector.py", line 16, in test_shouldInvokeConnectMethod
verify(mock_stomp, times=1).connect()
File "C:\Python27\lib\site-packages\mockito\invocation.py", line 111, in __call__
verification.verify(self, len(matched_invocations))
File "C:\Python27\lib\site-packages\mockito\verification.py", line 63, in verify
raise VerificationError("\nWanted but not invoked: %s" % (invocation))
VerificationError:
Wanted but not invoked: connect()
What did I do wrong?
You don't actually call the connect method on the mock object - you just check that it was called. This is what the error says as well Wanted but not invoked: connect(). Perhaps adding a call to mock_stomp.connect() before the call to verify will fix this:
mock_stomp = mock(Stomp(stomp_config))
# call the connect method first...
mock_stomp.connect()
connector = Connector()
connector.sendMessage()
# ...then check it was called
verify(mock_stomp, times=1).connect()
If you are instead trying to check that the mock is called from Connector, you probably at least need to pass in the mock_stomp object via dependency injection. For example
class Connector:
def __init__(self, stomp):
self.stomp = stomp
def sendMessage(self, msg):
self.stomp.connect()
# etc ...
and in your test
mock_stomp = mock(Stomp(stomp_config))
connector = Connector(mock_stomp)
connector.sendMessage()
verify(mock_stomp, times=1).connect()
Otherwise, I don't see how the connect() method could be invoked on the same instance of mock_stomp that you are basing your assertions on.

Python: block network connections for testing purposes?

I'm trying to test a package that provides interfaces to a few web services. It has a test suite that is supposed to test most functions without connecting to the internet. However, there are some lingering tests that may attempt to connect to the internet / download data, and I'd like to prevent them from doing so for two reasons: first, to make sure my test suite works if no network connection is available; second, so that I'm not spamming the web services with excess queries.
An obvious solution is to unplug my machine / turn off wireless, but when I'm running tests on a remote machine that obviously doesn't work.
So, my question: Can I block network / port access for a single python process? ("sandbox" it, but just blocking network connections)
(afaict, pysandbox doesn't do this)
EDIT: I'm using py.test so I need a solution that will work with py.test, in case that affects any proposed answers.
Monkey patching socket ought to do it:
import socket
def guard(*args, **kwargs):
raise Exception("I told you not to use the Internet!")
socket.socket = guard
Make sure this runs before any other import.
Update: There is now a pytest plugin that does the same thing as this answer! You can read the answer just to see how things work, but I strongly recommend using the plugin instead of copying-pasting my answer :-) See here: https://github.com/miketheman/pytest-socket
I found Thomas Orozco's answer to be very helpful. Following on keflavich, this is how I integrated into my unit test suite. This works for me with thousands of very different unit test-cases (<100 that need socket though) ... and in and out of doctests.
I posted it here. Including below for convenience. Tested with Python 2.7.5, pytest==2.7.0. (To test for yourself, run py.test --doctest-modules in directory with all 3 files cloned.)
_socket_toggle.py
from __future__ import print_function
import socket
import sys
_module = sys.modules[__name__]
def disable_socket():
""" disable socket.socket to disable the Internet. useful in testing.
.. doctest::
>>> enable_socket()
[!] socket.socket is enabled.
>>> disable_socket()
[!] socket.socket is disabled. Welcome to the desert of the real.
>>> socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Traceback (most recent call last):
...
RuntimeError: I told you not to use the Internet!
>>> enable_socket()
[!] socket.socket is enabled.
>>> enable_socket()
[!] socket.socket is enabled.
>>> disable_socket()
[!] socket.socket is disabled. Welcome to the desert of the real.
>>> socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Traceback (most recent call last):
...
RuntimeError: I told you not to use the Internet!
>>> enable_socket()
[!] socket.socket is enabled.
"""
setattr(_module, '_socket_disabled', True)
def guarded(*args, **kwargs):
if getattr(_module, '_socket_disabled', False):
raise RuntimeError("I told you not to use the Internet!")
else:
# SocketType is a valid public alias of socket.socket,
# we use it here to avoid namespace collisions
return socket.SocketType(*args, **kwargs)
socket.socket = guarded
print(u'[!] socket.socket is disabled. Welcome to the desert of the real.')
def enable_socket():
""" re-enable socket.socket to enable the Internet. useful in testing.
"""
setattr(_module, '_socket_disabled', False)
print(u'[!] socket.socket is enabled.')
conftest.py
# Put this in the conftest.py at the top of your unit tests folder,
# so it's available to all unit tests
import pytest
import _socket_toggle
def pytest_runtest_setup():
""" disable the interet. test-cases can explicitly re-enable """
_socket_toggle.disable_socket()
#pytest.fixture(scope='function')
def enable_socket(request):
""" re-enable socket.socket for duration of this test function """
_socket_toggle.enable_socket()
request.addfinalizer(_socket_toggle.disable_socket)
test_example.py
# Example usage of the py.test fixture in tests
import socket
import pytest
try:
from urllib2 import urlopen
except ImportError:
import urllib3
urlopen = urllib.request.urlopen
def test_socket_disabled_by_default():
# default behavior: socket.socket is unusable
with pytest.raises(RuntimeError):
urlopen(u'https://www.python.org/')
def test_explicitly_enable_socket(enable_socket):
# socket is enabled by pytest fixture from conftest. disabled in finalizer
assert socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Building on the very helpful answers from Thomas Orozco and driftcatcher here is a variant that works with Python's unittest and (after a small change) Django.
All you need to do is inherit your test case class from the enhanced NoSocketTestCase class and any access to the network will be detected and raises the SocketAccessError exception.
And this approach also works with Django. You only need to change the NoSocketTestCase class to inherit from django.test.TestCase instead of unittest.TestCase.
While not strictly answering OP's question I think this might be helpful for anyone who wants to block network access in unit tests.
no_sockets.py
import socket
from unittest import TestCase
class SocketAccessError(Exception):
pass
class NoSocketsTestCase(TestCase):
"""Enhancement of TestCase class that prevents any use of sockets
Will throw the exception SocketAccessError when any code tries to
access network sockets
"""
#classmethod
def setUpClass(cls):
cls.socket_original = socket.socket
socket.socket = cls.guard
return super().setUpClass()
#classmethod
def tearDownClass(cls):
socket.socket = cls.socket_original
return super().tearDownClass()
#staticmethod
def guard(*args, **kwargs):
raise SocketAccessError('Attempted to access network')
test_no_sockets.py
import urllib.request
from .no_sockets import NoSocketsTestCase, SocketAccessError
class TestNoSocketsTestCase(NoSocketsTestCase):
def test_raises_exception_on_attempted_network_access(self):
with self.assertRaises(SocketAccessError):
urllib.request.urlopen('https://www.google.com')
A simple way to put a gag on the requests library:
from unittest import mock
requests_gag = mock.patch(
'requests.Session.request',
mock.Mock(side_effect=RuntimeError(
'Please use the `responses` library to mock HTTP in your tests.'
))
)
with requests_gag:
... # no Internet here
httpretty is a small library that solves this problem.
If you are using Django test runner, write a custom test runner where you disable all 3rd party API calls.
# common/test_runner.py
import httpretty
from django.test.runner import DiscoverRunner
class CustomTestRunner(DiscoverRunner):
def run_tests(self, *args, **kwargs):
with httpretty.enabled(allow_net_connect=False):
return super().run_tests(*args, **kwargs)
add this new test runner to your settings
TEST_RUNNER = "common.test_runner.CustomTestRunner"
And from now on all external API calls have to be mocked or httpretty.errors.UnmockedError will be raised.
If you are using pytest, this fixture should work.
#pytest.fixture
def disable_external_api_calls():
httpretty.enable()
yield
httpretty.disable()
I have a pytest solution. pytest-network lybrary help me on this.
# conftest.py
import pytest
import socket
_original_connect = socket.socket.connect
def patched_connect(*args, **kwargs):
...
# It depends on your testing purpose
# You may want a exception, add here
# If you test unconnectable situations
# it can stay like this
#pytest.fixture
def enable_network():
socket.socket.connect = _original_connect
yield
socket.socket.connect = patched_connect
#pytest.fixture
def disable_network():
socket.socket.connect = patched_connect
yield
socket.socket.connect = _original_connect
# test_internet.py
def test_your_unconnectable_situation(disable_network):
response = request.get('http://stackoverflow.com/')
response.status_code == 400

Categories

Resources