I'm writing unit tests for a piece of code that uses zeep to access a SOAP API so I want to mock out zeep. In my actual code, it looks something like this:
from zeep import Client
def do_something():
client = Client("...")
In my test, I'm doing this:
from unittest import mock
#mock.patch('zeep.Client')
def test_do_somethi(self, MockedClient):
do_something()
The Client that the actual function is obtaining, is the actual zeep client and not my mock. I also tried:
#mock.patch('zeep.client.Client')
and the result was the same.
I also tried:
def test_do_something(self):
with mock.patch('zeep.client.Client') as MockedClient:
do_something()
with no difference.
Any ideas why this isn't working?
When mock doesn't work, the first thing to look for is if you are patching the right name. Three common import scenarios:
(a) If you want to mock out zeep but you import like
from zeep import Client
and your tests are in the same file, you patch Client not zeep.Client.
(b) If instead you import it like
import zeep
and then use zeep.Client in SUT code, then you patch zeep.Client.
(c) If you are testing code that lies in some other module (like mymodule) and you import zeep there with
from zeep import Client # (1)
then in your test module you
import mymodule
then you patch mymodule.Client, ... or mymodule.zeep.Client if you used the alternative import zeep form in (1).
You must patch the method/class from file you are using it. If you want to patch Client and it is imported in some_file.py you must import it from it, and not from lib (zeep.Client)
Here is an example using the official doc from Zeep.
some_lib.py
from zeep import Client
def do_something():
wsdl = 'http://www.soapclient.com/xml/soapresponder.wsdl'
client = zeep.Client(wsdl=wsdl)
return client.service.Method1('Zeep', 'is cool')
test_connection.py
from some_lib import do_something
from unittest.mock import patch
#patch('some_lib.Client')
def test_do_something(mock_zeep):
res = do_something()
assert mock_zeep.call_count == 1
if __name__ == '__main__':
test_soap_conn()
Related
I am trying to create unittests for my flask application which should assert exceptions properly.
I am attaching simplified code sample on what i want to test. The below test should finish as success.
def my_function():
abort(400,"error")
import unittest
from werkzeug import exceptions
class Tests(unittest.TestCase):
def test_event_link(self):
self.assertRaises(exceptions.BadRequest,my_function)
unittest.main(argv=[''], verbosity=2, exit=False)
I would simply patch the Flask abort function and ensure it is called with the correct value, this is preferable as it only tests your code not the behaviour of Flasks abort function which could change with future versions of Flask and break your tests.
See below example based on your code which also includes examples of testing the exception if this is what you would prefer to do.
# code.py
from flask import abort
def my_function():
abort(400, "error")
# test.py
import unittest
from unittest.mock import patch
from werkzeug import exceptions
import code # Your code file code.py
class Tests(unittest.TestCase):
#patch('code.abort')
def test_one(self, mock_abort):
code.my_function()
mock_abort.assert_called_once_with(400, 'error')
def test_two(self):
with self.assertRaises(exceptions.BadRequest):
code.my_function()
def test_three(self):
with self.assertRaisesRegexp(exceptions.BadRequest, '400 Bad Request: error'):
code.my_function()
unittest.main(argv=[''], verbosity=2, exit=False)
I am trying to mock the get function from requests.session and somehow it does not end up happening.
I have the following code:
#main.py
import requests
def function_with_get():
c = requests.session()
c.get('https://awesome_url.com')
# do some other stuff
return c
def second_function_with_get(client):
c.get('https://awesome_url.com')
# do some other stuff
#test.py
from unittest import mock
from django.test import TestCase
class Testing(TestCase):
#mock.patch('main.requests.session.get)
#mock.patch('main.requests.session)
def test_which_fails_because_of_get(mock_sess, mock_get):
client = function_with_get()
second_function_with_get(client)
assertEqual(mock_requests_session_get.call_count, 2)
The test throws an assertion error that mock_get is called 0 times (0 != 2)
How should the get function of requests.session() be mocked?
It seems that you are already mocking the requests session - since it is a MagicMock, you don't need to additionally mock the get method itself - checking for calls on the session will be enough.
So, your test.py could look like this:
#test.py
from unittest import mock
from unittest import TestCase
from main import function_with_get, second_function_with_get
class Testing(TestCase):
#mock.patch('main.requests.session')
def test_which_fails_because_of_get(self, mock_sess):
client = function_with_get()
second_function_with_get(client)
self.assertEqual(mock_sess.return_value.get.call_count, 2)
You could also try to create your own SessionMock class with the get method mocked, but it would require also proper setting (or resetting) it for each test. Personally, I usually find using MagicMock and its return_value chain easier.
I have the following code that I'm attempting to create a test (still work in progress):
from core.tests import BaseTestCase
from core.views import get_request
from entidades.forms import InstituicaoForm
from mock import patch
class InstituicaoFormTestCase(BaseTestCase):
def setUp(self):
super(InstituicaoFormTestCase, self).setUp()
#patch('get_request', return_value={'user': 'usuario_qualquer'})
def test_salva_instituicao_quando_informaram_convenio():
import pdb
pdb.set_trace()
form = InstituicaoForm()
it fails because when I try to create a InstituicaoForm, a get_request is called:
def get_request():
return getattr(THREAD_LOCAL, 'request', None)
and it trows this error
entidades/tests.py:11: in <module>
class InstituicaoFormTestCase(BaseTestCase):
entidades/tests.py:16: in InstituicaoFormTestCase
#patch('get_request', return_value={'user': 'usuario_qualquer'})
.tox/unit/local/lib/python2.7/site-packages/mock/mock.py:1670: in patch
getter, attribute = _get_target(target)
.tox/unit/local/lib/python2.7/site-packages/mock/mock.py:1522: in _get_target
(target,))
E TypeError: Need a valid target to patch. You supplied: 'get_request'
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
> /home/vinicius/telessaude/.tox/unit/local/lib/python2.7/site-packages/mock/mock.py(1522)_get_target()
-> (target,))
What am I doing wrong? How should mock this get_request() method?
I think the specific thing you're trying to do can be done like this:
#patch('core.views.get_request', return_value={'user': 'usuario_qualquer'})
But you should also look at the Django testing documentation, if you haven't already. You can use the testing client to fake the web request.
If you want to experiment with mock tests that don't access a database, check out Django Mock Queries. (I'm a small contributor to that project.) I've also tried mocking views, but it's fiddly.
I have a Python project that relies on a particular module, receivers.py, being imported.
I want to write a test to make sure it is imported, but I also want to write other tests for the behaviour of the code within the module.
The trouble is, that if I have any tests anywhere in my test suite that import or patch anything from receivers.py then it will automatically import the module, potentially making the test for import pass wrongly.
Any ideas?
(Note: specifically this is a Django project.)
One (somewhat imperfect) way of doing it is to use the following TestCase:
from django.test import TestCase
class ReceiverConnectionTestCase(TestCase):
"""TestCase that allows asserting that a given receiver is connected
to a signal.
Important: this will work correctly providing you:
1. Do not import or patch anything in the module containing the receiver
in any django.test.TestCase.
2. Do not import (except in the context of a method) the module
containing the receiver in any test module.
This is because as soon as you import/patch, the receiver will be connected
by your test and will be connected for the entire test suite run.
If you want to test the behaviour of the receiver, you may do this
providing it is a unittest.TestCase, and there is no import from the
receiver module in that test module.
Usage:
# myapp/receivers.py
from django.dispatch import receiver
from apples.signals import apple_eaten
from apples.models import Apple
#receiver(apple_eaten, sender=Apple)
def my_receiver(sender, **kwargs):
pass
# tests/integration_tests.py
from apples.signals import apple_eaten
from apples.models import Apple
class TestMyReceiverConnection(ReceiverConnectionTestCase):
def test_connection(self):
self.assert_receiver_is_connected(
'myapp.receivers.my_receiver',
signal=apple_eaten, sender=Apple)
"""
def assert_receiver_is_connected(self, receiver_string, signal, sender):
receivers = signal._live_receivers(sender)
receiver_strings = [
"{}.{}".format(r.__module__, r.__name__) for r in receivers]
if receiver_string not in receiver_strings:
raise AssertionError(
'{} is not connected to signal.'.format(receiver_string))
This works because Django runs django.test.TestCases before unittest.TestCases.
I'm writing unit tests to validate my project functionalities. I need to replace some of the functions with mock function and I thought to use the Python mock library. The implementation I used doesn't seem to work properly though and I don't understand where I'm doing wrong. Here a simplified scenario:
root/connector.py
from ftp_utils.py import *
def main():
config = yaml.safe_load("vendor_sftp.yaml")
downloaded_files = []
downloaded_files = get_files(config)
for f in downloaded_files:
#do something
root/utils/ftp_utils.py
import os
import sys
import pysftp
def get_files(config):
sftp = pysftp.Connection(config['host'], username=config['username'])
sftp.chdir(config['remote_dir'])
down_files = sftp.listdir()
if down_files is not None:
for f in down_files:
sftp.get(f, os.path.join(config['local_dir'], f), preserve_mtime=True)
return down_files
root/tests/connector_tester.py
import unittest
import mock
import ftp_utils
import connector
def get_mock_files():
return ['digital_spend.csv', 'tv_spend.csv']
class ConnectorTester(unittest.TestCase)
#mock.patch('ftp_utils.get_files', side_effect=get_mock_files)
def test_main_process(self, get_mock_files_function):
# I want to use a mock version of the get_files function
connector.main()
When I debug my test I expect that the get_files function called inside the main of connector.py is the get_mock_files(), but instead is the ftp_utils.get_files(). What am I doing wrong here? What should I change in my code to properly call the get_mock_file() mock?
Thanks,
Alessio
I think there are several problems with your scenario:
connector.py cannot import from ftp_utils.py that way
nor can connector_tester.py
as a habit, it is better to have your testing files under the form test_xxx.py
to use unittest with patching, see this example
In general, try to provide working minimal examples so that it is easier for everyone to run your code.
I modified rather heavily your example to make it work, but basically, the problem is that you patch 'ftp_utils.get_files' while it is not the reference that is actually called inside connector.main() but probably rather 'connector.get_files'.
Here is the modified example's directory:
test_connector.py
ftp_utils.py
connector.py
test_connector.py:
import unittest
import sys
import mock
import connector
def get_mock_files(*args, **kwargs):
return ['digital_spend.csv', 'tv_spend.csv']
class ConnectorTester(unittest.TestCase):
def setUp(self):
self.patcher = mock.patch('connector.get_files', side_effect=get_mock_files)
self.patcher.start()
def test_main_process(self):
# I want to use a mock version of the get_files function
connector.main()
suite = unittest.TestLoader().loadTestsFromTestCase(ConnectorTester)
if __name__ == "__main__":
unittest.main()
NB: what is called when running connector.main() is 'connector.get_files'
connector.py:
from ftp_utils import *
def main():
config = None
downloaded_files = []
downloaded_files = get_files(config)
for f in downloaded_files:
print(f)
connector/ftp_utils.py unchanged.