Python - Mocking an object's attribute with multiple values? - python

I have an object method which changes an attribute of an object. Another method (the one I'm trying to test) calls the first method multiple times and afterward uses the attribute that was modified. How can I test the second method while explicitly saying how the first method changed that attribute?
For example:
def method_to_test(self):
output = []
for _ in range(5):
self.other_method()
output.append(self.attribute_changed_by_other_method)
return output
I want to specify some specific values that attribute_changed_by_other_method will become due to other_method (and the real other_method uses probabilities in deciding on how to change attribute_changed_by_other_method).
I'm guessing the best way to do this would be to "mock" the attribute attribute_changed_by_other_method so that on each time the value is read it gives back a different value of my specification. I can't seem to find how to do this though. The other option I see would be to make sure other_method is mocked to update the attribute in a defined way each time, but I don't know of a particularly clean way of doing this. Can someone suggest a reasonable way of going about this? Thank you much.

What you can actually do is use flexmock for other_method. What you can do with flexmock is set a mock on an instance of your class. Here is an example of how to use it:
class MyTestClass(unittest.TestCase):
def setUp(self):
self.my_obj = MyClass()
self.my_obj_mock = flexmock(self.my_obj)
def my_test_case(self):
self.my_obj_mock.should_receive('other_method').and_return(1).and_return(2).and_return(3)
self.my_obj.method_to_test()
So, what is happening here is that on your instance of MyClass, you are creating a flexmock object out of self.my_obj. Then in your test case, you are stating that when you make your call to method_to_test, you should receive other_method, and each call to it should return 1, 2, 3 respectively.
Furthermore, if you are still interested in knowing how to mock out attribute_changed_by_other_method, you can use Mock's PropertyMock:
Hope this helps. Let me know how it goes!

For anyone still looking for a straightforward answer, this can be done easily with PropertyMock as the accepted answer suggests. Here is one way to do it.
from unittest.mock import patch, PropertyMock
with patch("method_your_class_or_method_calls", new_callable=PropertyMock) as mock_call:
mock_call.side_effect = [111, 222]
class_or_method()
Each subsequent call of that patched method will return that list in sequence.

Related

How to dynamically return Object attributes in python, including attributes of objects that are attributes

I am trying to write a testing program for a python program that takes data, does calculations on it, then puts the output in a class instance object. This object contains several other objects, each with their own attributes. I'm trying to access all the attributes and sub-attributes dynamically with a one size fits all solution, corresponding to elements in a dictionary I wrote to cycle through and get all those attributes for printing onto a test output file.
Edit: this may not be clear from the above but I have a list of the attributes I want, so using something to actually get those attributes is not a problem, although I'm aware python has methods that accomplish this. What I need to do is to be able to get all of those attributes with the same function call, regardless of whether they are top level object attributes or attributes of object attributes.
Python is having some trouble with this - first I tried doing something like this:
for string in attr_dictionary:
...
outputFile.print(outputclass.string)
...
But Python did not like this, and returned an AttributeError
After checking SE, I learned that this is a supposed solution:
for string in attr_dictionary:
...
outputFile.print(getattr(outputclass, string))
...
The only problem is - I want to dynamically access the attributes of objects that are attributes of outputclass. So ideally it would be something like outputclass.objectAttribute.attribute, but this does not work in python. When I use getattr(outputclass, objectAttribute.string), python returns an AttributeError
Any good solution here?
One thing I have thought of trying is creating methods to return those sub-attributes, something like:
class outputObject:
...
def attributeIWant(self,...):
return self.subObject.attributeIWant
...
Even then, it seems like getattr() will return an error because attributeIWant() is supposed to be a function call, it's not actually an attribute. I'm not certain that this is even within the capabilities of Python to make this happen.
Thank you in advance for reading and/or responding, if anyone is familiar with a way to do this it would save me a bunch of refactoring or additional code.
edit: Additional Clarification
The class for example is outputData, and inside that class you could have and instance of the class furtherData, which has the attribute dataIWant:
class outputData:
example: furtherData
example = furtherData()
example.dataIWant = someData
...
with the python getattr I can't access both attributes directly in outputData and attributes of example unless I use separate calls, the attribute of example needs two calls to getattr.
Edit2: I have found a solution I think works for this, see below
I was able to figure this out - I just wrote a quick function that splits the attribute string (for example outputObj.subObj.propertyIWant) then proceeds down the resultant array, calling getattr on each subobject until it reaches the end of the array and returns the actual attribute.
Code:
def obtainAttribute(sample, attributeString: str):
baseObj = sample
attrArray = attributeString.split(".")
for string in attrArray:
if(attrArray.index(string) == (len(attrArray) - 1)):
return getattr(baseObj,string)
else:
baseObj = getattr(baseObj,string)
return "failed"
sample is the object and attributeString is, for example object.subObject.attributeYouWant

Python Mocking assert_called not working

I am able to successfully mock a function, and I am sure that the original is not called. I added a huge print statement to the original function and when I mock it, this print is not called. When I turn the mock back on, the print statement is not called.
However, my assert_called is failing saying it was never called. Has anyone ever experienced something like this?
class FooTestCase(unittest.TestCase):
#mock.patch('MyObj.helper_function')
def test_simple(self, mock_hf):
my_obj = MyObj()
# internally, this class imports HelperModule
# and the method calls helper_function
my_obj.do_something()
mock_hf.helper_function.assert_called()
return
My error response
AssertionError: Expected 'helper_function' to have been called.
Update
I just added the following lines right before the assertion
print mock_cw.method_calls
print mock_cw.mock_calls
method_calls is an empty list, while mock_calls is a list with 1 item which is
[call(arg1_expected_for_helper_fn, arg2_expected_for_helper_fn)]
Yet the assert still fails
Usually an error like this is a result of not patching the correct location. Try to patch the object itself with this:
#patch.object(MyObj, "helper_function")
def test_simple(mock_hf):
...
Since MyObj is (assumed to be) imported at the top of the test file, this patches the method on that object directly.
The issue is that I was checking to see if mock_hf.helper_function was called, but mock_hf is already mapped to the helper_function. I was more or less checking that helper_function.helper_function was called rather than just helper_function.
The assert line needed to be
mock_hf.assert_called()
I see the original poster has done this, but for anyone else stumbling on this as I did...
Don't forget you need to wrap your expected calls in a call object e.g.
mock_logger.assert_has_calls([call(expected_log_message_1), call(expected_log_message_2)])
If you don't do that, it will complain that the expected call did not happen and you will spend ages comparing the output to try and work out why (as I did!).

How to test if a method is called using pytest

I want to write a unit test to check if a method is being called. Is there any way to do it. Or i am misunderstanding the way mock can be used here? As this way mocked_method is always called but without any args.
#(pytest.parameterize)
def test(jsonrpc_proxy):
jsonrpc_proxy.method1_call()
# Method 1 should not call method 2
with mock.patch('method2') as mocked_method:
assert ((args),) not in mocked_track.call_args_list
# do something
jsonrpc_proxy.method1_call()
# Method 1 should call method 2
with mock.patch('method2') as mocked_method:
assert ((args),) in mocked_track.call_args_list
PS: I have checked other questions related to it before posting, but i think i am misunderstanding the whole concept about how we use mock in such scenarios. Please enlighten me as i am new to this.
you need to call method1 when method2 is patched, not before that.
try moving the call inside the with statement:
with mock.patch('method2') as mocked_method:
jsonrpc_proxy.method1_call()
assert ((args),) not in mocked_track.call_args_list

How to make sure that a function doesn't get called using Mock in a Django Application

I've already browsed through the mock documentations, and i've seen some examples where mock has been used. But, being a newbie, i find it hard to use mock in my tests.
test_scoring.py - I am creating a test to make sure that a function DOES NOT get called whenever i create an item.
The function compute_score() that i'd like to mock is part of a class HistoryItem.
What i got so far is this :
#test_scoring.py
#mock.patch('monitor.report.history_item.HistoryItem.compute_score')
def test_save_device_report(self):
....
result = factory.create_history_item(jsn)
# If the mocked method gets called after the above function is used, then there should be an error.
So, how do I mock the method? I'm quite confused on how to use it, because there are different ways in the resources i found.
I'd really appreciate your help.
When using the patch method as a decorator, you need to specify a second parameter to your test function:
#mock.patch('monitor.report.history_item.HistoryItem.compute_score')
def test_save_device_report(self, my_mock_compute_score):
....
# Assuming the compute_score method will return an integer
my_mock_compute_score.return_value = 10
result = factory.create_history_item(jsn)
# Then simulate the call.
score = result.compute_score() # This call could not be necessary if the previous
# call (create_history_item) make this call for you.
# Assert method was called once
my_mock_compute_score.assert_called_once()
# Also you can assert that score is equal to 10
self.assertEqual(score, 10)
Note that the mocks should be used only when you've tested the patched method or object in another test.
Where to patch? -> https://docs.python.org/3/library/unittest.mock.html#where-to-patch
Edit
This patch is gonna to avoid a real call to compute_score(). However, after reread your post I can see you want to assert your function doesn't get called.
Hopefully, the called attribute is present in every mock you make, so for that you can use:
#mock.patch('monitor.report.history_item.HistoryItem.compute_score')
def test_save_device_report(self, my_mock_compute_score):
...
result = factory.create_history_item(jsn)
self.assertFalse(my_mock_compute_score.called)
...

AssertionError, altough the expected call looks same as actual call

I made a command in django which calls a function.
That function does a django orm call:
def get_notes():
notes = Note.objects.filter(number=2, new=1)
return [x.note for x in notes]
I want to patch the actual lookup:
#mock.patch('Note.objects.filter', autospec=True)
def test_get_all_notes(self, notes_mock):
get_notes()
notes_mock.assert_called_once_with(number=2, new=1)
I get the following assertion error:
AssertionError: Expected call: filter(number=2, new=1)
Actual call: filter(number=2, new=1)
I search on google and stackoverflow for hours, but I still haven't a clue.
Can anyone point me in the right direction, I think it might be an obvious mistake I'm making...
AFAIK you can't use patch() like this. Patch target should be a string in the form package.module.ClassName. I don't know much about django but I suppose Note is a class so Note.objects.filter is not something you can import and hence use in patch(). Also I don't think patch() can handle attributes. Actually I don't quite understand why the patch works at all.
Try using patch.object() which is specifically designed to patch class attributes. It implies Note is already imported in your test module.
#mock.patch.object(Note, 'objects')
def test_get_all_notes(self, objects_mock):
get_notes()
objects_mock.filter.assert_called_once_with(number=2, new=1)
I've removed autospec because I'm not sure it will work correctly in this case. You can try putting it back if it works.
Another option might be to use patch() on whatever you get with type(Note.objects) (probably some django class).
As I've said I don't know much about django so I'm not sure if these things work.

Categories

Resources