I am trying to write a test in Python that checks if a method in a class that I am writing sets the attribute value for a dataset in some Hdf file. The logic is the following: An instance of the class is constructed by passing an instance of h5py.File, then one method creates a dataset inside this file. In the next step I have another method that sets certain attributes for this dataset.
What I am trying to test is if my class method create_attributes(self,attributes) sets the field hdf_file[dset_name].attrs[attr_name] to some value that is passed in the variable attributes. However, I would like to avoid to actually create a Hdf file. So far I have tried to mock an instance of a hdf file and work with that. The minimal working code example would be the following:
import h5py
class TestSomething:
#mock.patch('h5py.File')
def test_if_attr_is_initialized(self,mock_hdf):
# Here I would like to call a function that basically executes
# the following line:
mock_hdf['test_dset'].attrs['test_field']='value'
# Then I want to check if the attribute field has been assigned
assert mock_hdf['test_dset'].attrs['test_field']=='value'
Can anybody help me finding the correct thing to do to check whether or not the attribute in the hdf file is set correctly? Any help would be greatly appreciated, I am a complete newbie to all the mocking techniques.
Edit:
In the following I am providing a minimal code example for both the class, and the respective test as requeseted by wwii:
import h5py
class HdfWriter():
def __init__(self,hdf_file):
self.hdf_file=hdf_file
def create_attrs(self,attributes):
dset_name=attributes.keys()[0]
attrs=attributes[dset_name]
for key in attrs:
self.hdf_file[dset_name].attrs[key]=attrs[key]
Please note here that with a real hdf file I would first have to create a dataset but I would like to leave that for another test. The following test should just check, whether for a hypothetical hdf file, which has the dataset test_dset the attributes for this data set are written:
import h5py
import HdfWriter
class TestSomething:
#mock.patch('h5py.File')
def test_if_attr_is_initialized(self,mock_hdf):
writer=hw.HdfWriter(mock_hdf)
attr={'test_dset':{'test_field':'test_value'}}
writer.create_attrs(attr)
assert writer.hdf_file['test_dset'].attrs['test_field']=='value'
Mocking h5py.File
class HdfWriter():
def __init__(self,hdf_file):
self.hdf_file=hdf_file
def create_attrs(self,attributes):
dset_name=attributes.keys()[0]
attrs=attributes[dset_name]
for key in attrs:
self.hdf_file[dset_name].attrs[key]=attrs[key]
For the purpose of the create_attrs method, hdf_file behaves as a dictionary that returns an object that also behaves like a dictionary. The docs explain pretty clearly how to mock a dictionary.
You need a mock that has an attrs attribute that behaves like a dictionary:
import mock
attrs_d = {}
def setattrs(name, value):
## print 'setattrs', name, value
attrs_d[name] = value
def getattrs(name):
## print 'getattrs', name
return attrs_d[name]
mock = mock.MagicMock()
mock.attrs.__setitem__.side_effect = setattrs
mock.attrs.__getitem__.side_effect = getattrs
You need a mock for hdf_file that behaves like a dictionary and will return the mock object created above.
hdf_d = {'test_dset':mock}
def getitem(name):
## print 'getitem', name
return hdf_d[name]
def setitem(name, value):
hdf_d[name] = value
mock_hdf = mock.MagicMock()
mock_hdf.__getitem__.side_effect = getitem
mock_hdf.__setitem__.side_effect = setitem
hdf_d, as implemented, only works for the key 'test_dset'. Depending on your needs it may be better for getitems to just return mock regardless of the name argument.
def test_if_attr_is_initialized(mock_hdf):
writer=HdfWriter(mock_hdf)
attr={'test_dset':{'test_field':'test_value'}}
writer.create_attrs(attr)
print writer.hdf_file['test_dset'].attrs['test_field'], '==', attr['test_dset']['test_field']
assert writer.hdf_file['test_dset'].attrs['test_field']=='test_value'
test_if_attr_is_initialized(mock_hdf)
>>>
test_value == test_value
>>>
This should suffice to test create_attrs but it may not be optimal - maybe someone will chime in with some refinements.
Related
I've a function that I want to write unit tests for (function is a part of class A):
def funcA(self):
variable = self.attributes.method()
logger.info("Log")
this function is called from another function that initialize Class and save its object to self.attributes variable.
def funcB(self):
self.attributes = B()
# call funcA if certain criteria is met
and I have implementation of method function inside of B class definition.
How can I now write unit test for funcA?
I've tried to do it like this:
import classA as a
def test_funcA(self) -> None:
self.classA = a.A()
method_return = self.classA.funcA()
this gives me error 'A' object has no attribute 'attributes'
I also tried to mock this missing attribute by:
a.A.attributes = Mock(return_value={})
but then I got different error TypeError: Object of type Mock is not JSON serializable
I guess I need to somehow mock class B and the method function, so that my main method that I'm testing will just take the mocked return and assert if it logs correct message, right?
Your initial intuition about setting the attributes directly is probably the way to go. You should set the proper value - with the relevant properties B() would have returned.
Inside your test, you can do something in the form of self.classA.attributes = Bunch(mathod=lambda: "value for variable", another_attribute_used_inside_funcA = 20). See bunch library.
As far as I am concerned, first class usually means that:
Can be stored in a variable
Can be passed to or returned from a function
Can form complex data structure like list
So, as for Python where everything is an object, everything is first class citizen. Am I right? Even functions are first class citizens.
# functions
# can be treated as objects
def toUpper(text):
return text.upper()
print toUpper('abc')
Upperize = toUpper
print Upperize('abc')
What about R? Is everything in R is first class object? Or is there any exception?
Any thoughts would be helpful.
I have situation similar to:
class BaseClient(object):
def __init__(self, api_key):
self.api_key = api_key
# Doing some staff.
class ConcreteClient(BaseClient):
def get_some_basic_data(self):
# Doing something.
def calculate(self):
# some staff here
self.get_some_basic_data(param)
# some calculations
Then I want to test calculate function using mocking of get_some_basic_data function.
I'm doing something like this:
import unittest
from my_module import ConcreteClient
def my_fake_data(param):
return [{"key1": "val1"}, {"key2": "val2"}]
class ConcreteClientTest(unittest.TestCase):
def setUp(self):
self.client = Mock(ConcreteClient)
def test_calculate(self):
patch.object(ConcreteClient, 'get_some_basic_data',
return_value=my_fake_data).start()
result = self.client.calculate(42)
But it doesn't work as I expect.. As I thought, self.get_some_basic_data(param) returns my list from my_fake_data function, but it looks like it's still an Mock object, which is not expected for me.
What is wrong here?
There are two main problems that you are facing here. The primary issue that is raising the current problem you are experiencing is because of how you are actually mocking. Now, since you are actually patching the object for ConcreteClient, you want to make sure that you are still using the real ConcreteClient but mocking the attributes of the instance that you want to mock when testing. You can actually see this illustration in the documentation. Unfortunately there is no explicit anchor for the exact line, but if you follow this link:
https://docs.python.org/3/library/unittest.mock-examples.html
The section that states:
Where you use patch() to create a mock for you, you can get a
reference to the mock using the “as” form of the with statement:
The code in reference is:
class ProductionClass:
def method(self):
pass
with patch.object(ProductionClass, 'method') as mock_method:
mock_method.return_value = None
real = ProductionClass()
real.method(1, 2, 3)
mock_method.assert_called_with(1, 2, 3)
The critical item to notice here is how the everything is being called. Notice that the real instance of the class is created. In your example, when you are doing this:
self.client = Mock(ConcreteClient)
You are creating a Mock object that is specced on ConcreteClient. So, ultimately this is just a Mock object that holds the attributes for your ConcreteClient. You will not actually be holding the real instance of ConcreteClient.
To solve this problem. simply create a real instance after you patch your object. Also, to make your life easier so you don't have to manually start/stop your patch.object, use the context manager, it will save you a lot of hassle.
Finally, your second problem, is your return_value. Your return_value is actually returning the uncalled my_fake_data function. You actually want the data itself, so it needs to be the return of that function. You could just put the data itself as your return_value.
With these two corrections in mind, your test should now just look like this:
class ConcreteClientTest(unittest.TestCase):
def test_calculate(self):
with patch.object(ConcreteClient, 'get_some_basic_data',
return_value=[{"key1": "val1"}, {"key2": "val2"}]):
concrete_client = ConcreteClient(Mock())
result = concrete_client.calculate()
self.assertEqual(
result,
[{"key1": "val1"}, {"key2": "val2"}]
)
I took the liberty of actually returning the result of get_some_basic_data in calculate just to have something to compare to. I'm not sure what your real code looks like. But, ultimately, the structure of your test in how you should be doing this, is illustrated above.
I have the following scenario:
in my models.py
class FooBar(models.Model):
description = models.CharField(max_length=20)
in my utils.py file.
from models import FooBar
def save_foobar(value):
'''acts like a helper method that does a bunch of stuff, but creates a
FooBar object and saves it'''
f = FooBar(description=value)
f.save()
in tests.py
from utils import save_foobar
#patch('utils.FooBar')
def test_save_foobar(self, mock_foobar_class):
save_mock = Mock(return_value=None)
mock_foobar_class.save = save_mock
save_foobar('some value')
#make sure class was created
self.assertEqual(mock_foobar_class.call_count, 1) #this passes!!!
#now make sure save was called once
self.assertEqual(save_mock.call_count, 1) #this fails with 0 != 1 !!!
This is a simplified version of what I'm trying to do... so please don't get hungup on why I have a utils file and a helper function for this (in real life it does several things). Also, please note, while simplified, this is an actual working example of my problem. The first call to test call_count returns 1 and passes. However, the second one returns 0. So, it would seem like my patch is working and getting called.
How can I test that not only an instance of FooBar gets created, but also that the save method on it gets called?
Here is your problem, you currently have:
mock_foobar_class.save = save_mock
since mock_foobar_class is a mocked class object, and the save method is called on an instance of that class (not the class itself), you need to assert that save is called on the return value of the class (aka the instance).
Try this:
mock_foobar_class.return_value.save = save_mock
I hope that helps!
Do python class-methods have a method/member themselves, which indicates the class, they belong to?
For example ...:
# a simple global function dummy WITHOUT any class membership
def global_function():
print('global_function')
# a simple method dummy WITH a membership in a class
class Clazz:
def method():
print('Clazz.method')
global_function() # prints "global_function"
Clazz.method() # prints "Clazz.method"
# until here, everything should be clear
# define a simple replacement
def xxx():
print('xxx')
# replaces a certain function OR method with the xxx-function above
def replace_with_xxx(func, clazz = None):
if clazz:
setattr(clazz, func.__name__, xxx)
else:
func.__globals__[func.__name__] = xxx
# make all methods/functions print "xxx"
replace_with_xxx(global_function)
replace_with_xxx(Clazz.method, Clazz)
# works great:
global_function() # prints "xxx"
Clazz.method() # prints "xxx"
# OK, everything fine!
# But I would like to write something like:
replace_with_xxx(Clazz.method)
# instead of
replace_with_xxx(Clazz.method, Clazz)
# note: no second parameter Clazz!
Now my question is: How is it possible, to get all method/function calls print "xxx", WITHOUT the "clazz = None" argument in the replace_with_xxx function???
Is there something possible like:
def replace_with_xxx(func): # before it was: (func, clazz = None)
if func.has_class(): # something possible like this???
setattr(func.get_class(), func.__name__, xxx) # and this ???
else:
func.__globals__[func.__name__] = xxx
Thank you very much for reading. I hope, i could make it a little bit clear, what i want. Have a nice day! :)
I do not think this is possible and as a simple explanation why we should think about following: you can define a function and attach it to the class without any additional declarations and it will be stored as a field of the class. And you can assign the same function as a class method to 2 or more different classes.
So methods shouldn't contain any information about the class.
Clazz.method will have an attribute im_class, which will tell you what the class is.
However, if you find yourself wanting to do this, it probably means you are doing something the hard way. I don't know what you are trying to accomplish but this is a really bad way to do just about anything unless you have no other option.
For methods wrapped in #classmethod, the method will be bound and contain the reference im_self pointing to the class.