I'm not sure what I'm doing wrong. Perhaps I have the wrong end of the stick with mocking. But my assumption was that when you use mocks it basically does some magic and replaces objects in your original code.
sites.py
class Sites:
def __init__(self):
pass
def get_sites(self):
return ['washington', 'new york']
my_module.py
from mylib import sites
def get_messages():
# get Sites
sm = sites.Sites()
sites = sm.get_sites()
print('Sites:' , sites)
for site in sites:
print('Test: ' , site)
my_test.py
import my_module
import unittest
from unittest.mock import patch
class MyModuleTestCase(unittest.TestCase):
#patch('my_module.Sites')
def test_process_the_queue(self, mock_sites):
mock_sites.get_sites.return_value = ['london', 'york']
print(mock_sites.get_sites())
my_module.get_messages()
if __name__ == '__main__':
unittest.main()
Running this I get the following output:
.['london', 'york']
Sites: <MagicMock name='Sites().get_sites()' id='139788231189504'>
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
[Finished in 0.1s]
I was expecting the second print output (which occurs within my_module.py) to be the same as the first and to loop through the list I passed through as a return value.
Any help would be greatly appreciated.
Updated
To show how I was originally importing my class
Python mock, while silly powerful, is definitely not very intuitive to use.
The print statement shows that you are patching my_module.Sites correctly but you have not registered the get_sites return value correctly, and it should be:
mock_sites.return_value.get_sites.return_value = ['london', 'york']
The print statement shows that there was a call to Sites().get_sites() registered on your patched object:
Sites: <MagicMock name='Sites().get_sites()' id='139788231189504'>
When reading this I find it helpful to translate () to return_value
Sites.return_value.get_sites.return_value
The return value you are missing represents the instantiation of the mock sites object: Sites().
The problem I was having was with the way that I was importing and calling my external class.
from mylib import sites
sm = sites.Sites()
Mock is much happier when you use:
from mylib.sites import Sites
sm = Sites()
This along with dm03514's answer helped me to get it working
Related
I'm asked to develop unit tests for a program which is such badly developed that the tests don't run... but the program does. Thus, I need to explain the reason why and I actually don't know!
Here is a piece of code that intends to represent the code I need to test:
from services import myModule1
from services.spec1 import importedFunc
from services.spec2 import getTool
from services.spec3 import getDict
class myClass(object):
def __init__(self, param1, param2):
self.param1 = param1
self.param2 = param2
self.param3 = 0
self.param4 = 0
def myMethod(self):
try:
myVar1 = globalDict['key1']
myVar2 = globalDict['key2']
newVar = importedFunc(par1=myVar1, par2=myVar2, par3=extVar3)
calcParam = myModule1.methodMod1(self.param1)
self.param3 = calcParam["keyParam3"]
self.param4 = newVar.meth1(self.param2)
globTools.send_message(self.param3, self.param4)
except:
globTools.error_message(self.param3, self.param4)
return
class myClass2(object):
def __init__(self, *myclass2_params):
# some piece of code to intialize dedicated attributes
self.add_objects()
def add_objects(self):
# Some piece of code
my_class = myClass(**necessary_params)
# Some piece of code
return
if __name__ == '__main__':
globTools = getTool("my_program")
globalDict = getDict(some_params)
# Some piece of code
my_class2 = myClass2(**any_params)
# Some piece of code
As you can see, the problem is that the class and its methods uses global variables, defined in the main scope. And it's just a quick summary because it's actually a bit more complicated, but I hope it's enough to give you an overview of the context and help me understand why the unit test fail.
I tried to mock the imported modules, but I did not manage to a successful result, so I first tried to make it simple and just initialize all parameters.
I went to this test file:
import unittest
from my_module import myClass
from services import myModule1
from services.spec1 import importedFunc
from services.spec2 import getTool
from services.spec3 import getDict
def test_myClass(unittest.TestCase):
def setUp(self):
globTools = getTool("my_program")
globalDict = getDict(some_params)
def test_myMethod(self):
test_class = myClass(*necessary_parameters)
test_res = test_class.myMethod()
self.assertIsNotNone(test_res)
if __name__ == '__main__':
unittest.main()
But the test fail, telling me 'globTools is not defined' when trying to instantiate myClass
I also tried to initialize variables directly in the test method, but the result is the same
And to be complete about the technical environment, I cannot run python programs directly and need to launch a docker environment via a Jenkins pipeline - I'm not very familiar with this but I imagine it should not have an impact on the result
I guess the problem comes from the variable's scopes, but I'm not able to explain it in this case: why the test fail where as the method itself works (yes, it actually works, or at least the program globally runs without)
It's not as bad as you think. Your setUp method just needs to define the appropriate top-level globals in your module, rather than local variables.
import unittest
import my_module
from my_module import myClass
from services import myModule1
from services.spec1 import importedFunc
from services.spec2 import getTool
from services.spec3 import getDict
class test_myClass(unittest.TestCase):
def setUp(self):
my_module.globTools = getTool("my_program")
my_module.globalDict = getDict(some_params)
def test_myMethod(self):
test_class = myClass(*necessary_parameters)
test_res = test_class.myMethod()
self.assertIsNotNone(test_res)
if __name__ == '__main__':
unittest.main()
Depending on how the code uses the two globals, setUpClass might be a better place to initialize them, but it's probably not worth worrying about. Once you have tests for the code, you are in a better position to remove the dependency on these globals from the code.
I am using cocotb version 1.5.2, and I would like to write an utility function to create reports/plots per test.
MWE: Implementing the get_test_name function so that the following test will print my_wonderful_test.
import cocotb
#cocotb.test()
async def my_wonderful_test(dut):
print(get_test_name(dut));
def get_test_name(dut):
pass # how do I get the current test from here?
You can use "name" attribute :
import cocotb
#cocotb.test()
async def my_wonderful_test(dut):
print(my_wonderful_test.name);
But not sure that exactly you want.
Thank you for the commenters, and thank you #FabienM for trying to give an answer. Thank you for #Tzane for trying to find an answer. You were close.
If you want to know a one liner answer
import cocotb;
def get_test_name():
return cocotb.regression_manager._test.name
but the underline prefix in _test maybe it will break in the future, but for since I was only concerned about version 1.5.2 this is OK for me.
Any way I implemented another method that scans the stack one level at a time and check if the frame is in a cocotb.test decorated function. This is also the method that cocotb uses to _discover_tests
It won't work if the test is in a closure, but I never use that, and I don't know if it is even supported.
import cocotb
import inspect;
import sys
#cocotb.test()
async def test_get_testname(dut):
print('Runnign from test ', get_test_name())
def get_test_name():
try:
return cocotb.regression_manager._test.name
except:
pass
cocotbdir = '/'.join(cocotb.__file__.split('/')[:-1])
frame = sys._getframe();
prev_frame = None
while frame is not None:
try:
# the [documentation](https://docs.python.org/2/library/inspect.html#inspect.getmodule)
# says
# Try to guess which module an object was defined in.
# Implying it may fail, wrapp in a try block and everything is fine
module = inspect.getmodule(frame.f_code)
func_name = inspect.getframeinfo(frame).function
if hasattr(module, func_name):
ff = getattr(module, func_name)
if isinstance(ff, cocotb.test):
return func_name
except:
pass
prev_frame = frame;
frame = frame.f_back;
# return None if fail to determine the test name
I don't know why my questions are so badly received
It was something simple that I preferred to engage more people
I have the following project structure:
root/
|-mylib/
|-tests/
| |-__init__.py
| |-test_trend.py
|-__init__.py
|-data.py
|-trend.py
In trend.py:
from mylib.data import get_item
def get_trend(input: str):
item = get_item(input)
return f"Trend is: {item}"
Inside data.py:
def get_item(id):
print("ORIGINAL")
return get_item_from_db(id)
Testing
I want to test get_trend in isolation, therefore I patch get_item inside: test_trend.py:
import unittest
from unittest.mock import patch
from mylib.trend import get_trend
def p__get_item(input):
return 0
class MyTestCase(unittest.TestCase):
#patch("mylib.data.get_item", new=p__get_item)
def test_trend(self):
v = get_trend()
self.assertEqual(v, "Trend is: 0")
But when I run the tests (the command is run from inside the root directory):
python -m unittest discover
I see that the log shows that the original get_item is called. The test is failing of course.
What am I doing wrong?
Attempt 1
If I try this other flavor of the patch APIL
class MyTestCase(unittest.TestCase):
#patch("mylib.data.get_item")
def test_trend(self, mocked_fun):
mocked_fun.return_value = 0
v = get_trend()
self.assertEqual(v, "Trend is: 0")
It still does not work. In the console log I can see ORIGINAL being printed and the test fails.
Experiment 1
If I change the target in #patch to a non existing attribute like:
#patch("mylib.data.non_existing", new=p__get_item)
I actually get an error from the library saying the module does not contain such attribute. So, it seems like mylib.data.get_item is correctly being targeted, but still the patching is not happening.
Explanation
get_item is called in mylib.trend module, however, you patched it in mylib.data module, which is the wrong place. patch works by replacing objects on import. In this case, you want to patch get_item inside mylib.trend module not mylib.data.
Solution
class MyTestCase(unittest.TestCase):
#patch("mylib.trend.get_item")
def test_trend(self, mocked_get_item):
mocked_get_item.return_value = 0
...
Notes
Further explanation on where to patch can be found on the official python documentation here.
I'm new to Python, but I've done quite a bit of unit testing in C# and JavaScript. I'm having trouble figuring out the mocking framework in Python. Here's what I have (trimmed down):
invoice_business.py
import ims.repository.invoice_repository as invoiceRepository
import logging
logger = logging.getLogger(__name__)
def update_invoice_statuses(invoices):
for invoice in invoices:
dbInvoice = invoiceRepository.get(invoice.invoice_id)
print("dbInvoice is %s" % dbInvoice) #prints <MagicMock etc.>
if dbInvoice is None:
logger.error("Unable to update status for invoice %d" % invoice.invoice_id)
continue;
test_invoice_business.py
from unittest import TestCase, mock
import logging
import ims.business.invoice_business as business
class UpdateInvoiceTests(TestCase):
#mock.patch("ims.business.invoice_business.invoiceRepository")
#mock.patch("ims.business.invoice_business.logger")
def test_invoiceDoesNotExist_logsErrorAndContinues(self, invoiceRepoMock, loggerMock):
#Arrange
invoice = Invoice(123)
invoice.set_status(InvoiceStatus.Filed, None)
invoiceRepoMock.get.return_value(33)
#Act
business.update_invoice_statuses([invoice])
#Assert
invoiceRepoMock.get.assert_called_once_with(123)
loggerMock.error.assert_called_once_with("Unable to update status for invoice 123")
The test fails with
AssertionError: Expected 'get' to be called once. Called 0 times.
The print statement in update_invoice_statuses gets hit, though, because I see the output of
dbInvoice is <MagicMock name='invoiceRepository.get()' id='xxxx'>
Any idea what I'm doing wrong here?
Edit: After #chepner's help, I ran into another assertion error and realized it was because I should be using invoiceRepoMock.get.return_value = None rather than .return_value(None)
The mock arguments to your test function are swapped. The inner decorator (for the logger) is applied first, so the mock logger should be the first argument to your method.
#mock.patch("ims.business.invoice_business.invoiceRepository")
#mock.patch("ims.business.invoice_business.logger")
def test_invoiceDoesNotExist_logsErrorAndContinues(self, loggerMock, invoiceRepoMock):
...
I'm trying to use patch to return the a Mock from within a method. The basic structure is as follows:
MyCode.py
class MyClass:
def __init__(self, first_name, last_name):
self.first = first_name
self.last = last_name
def get_greeting(self):
return 'Hello {f} {l}'.format(f=self.first, l=self.last)
def get_new_greeting(first_name, last_name):
obj = MyClass(first_name, last_name)
return obj.get_greeting()
my_code_test.py
import unittest
from mock import Mock, patch
import my_code
class TestMyCode(unittest.TestCase):
def setUp(self):
pass
#patch('my_code.MyClass')
def test_get_greeting(self, MockClass):
instance = MockClass.return_value
mock_greeting = 'Hello Me'
instance.get_greeting.return_value = mock_greeting
greeting = my_code.get_new_greeting('john', 'doe')
self.assertEqual(greeting, mock_greeting)
if __name__ == '__main__':
unittest.main()
The code above works fine for me. However, when I apply the same pattern to the real code that I'm trying to test, the real object (not the mock one) gets returned in the method being tested. I can't see any differences. The only think that is a bit different is that the real class is defined in a init.py file. I'm not sure if this make a difference or not? Has any seen this before?
Note: the actual lib is twilio 3.3.5 and I'm using Python 2.6.5 and Django 1.3.1 and Mock 0.7.2
I figured it out. It had nothing to do with __init__.py file. It was (as usual) my fault! :)
Just for anyone that is every trying to use Mock and patch with Twilio and SMS in the future, here is the solution:
I was Mocking the class twilio.rest.TwilioRestClient But, things are chained together and I needed to call patch on the inner class called SmsMessage. So, for my unit test, this works well:
#patch('twilio.rest.resources.SmsMessages')
def test_send_msg_valid_args(self, MockClass):
instance = MockClass.return_value
instance.create.return_value = None
to_number = '+15555555555'
msg = 'Hello world'
send_sms(to_number, msg)
instance.create.assert_called_once_with(to=to_number, body=msg, from_=default_from_number)
note: send_sms is really the function that I'm trying to test. I just wanted to make sure that it was calling twilio as expected and supplying the default_from_number. The value default_from_number is defined in the settings file and not really important for this example.