Reputation: 2826
Could someone help me understand the difference between unitest.patch
instantianed objects and classes? I'm trying to mock calls to sagemaker.Session.describe_training_job
.
I have a function that calls my_session = sagemaker.Session()
then calls my_session.describe_training_job()
later:
#my_module.py
import sagemaker
def do_something_with_session(local_session, job):
return local_session.describe_training_job(job)
def my_function(args):
my_session = sagemaker.Session()
description = do_something_with_session(my_session, args.job)
If I use the test below, the my_session
object is a MagicMock as expected, but does not have the describe_training_job
method:
@patch("sagemaker.Session")
def test_class(self, sagemaker_session):
sagemaker_session.describe_training_job = MagicMock(return_value=self.mock_return_value)
my_module.my_function(args=self.mock_args)
If I instead do this:
@patch("sagemaker.Session")
def test_class(self, sagemaker_session):
# Need to instantiate an object for this to work
session_object = sagemaker_session()
session_object.describe_training_job = MagicMock(return_value=self.mock_return_value)
my_module.my_function(self.mock_args)
Then the test works as expected: the my_session
object is a MagicMock with a describe_training_job
method that always returns the value I set it to.
Could someone help understand what the behavior is here? What I've noticed when is that the name
parameter of the MagicMock is Session
when I try to use the class, but Session()
when I do instantiate the object. Not sure how that affects binding.
Upvotes: 1
Views: 1737
Reputation: 16855
A mock is a placeholder object, it does not have the mocked methods (though it may know which methods can be called if using autospec). Every method call on the mock that you do not explicitly provide just returns another mock object.
If you mock a class, and that class is instantiated in the tested code, the resulting instance will be another mock. The instantation is just a __call__
for the mock, which returns another mock as every call does (and it always returns the same mock for the same call). Similar to the result of a function call, this mock can be accessed via return_value
.
If you set an attribute on the class mock like in your first example, it is only bound to that class mock, not to the instance mock, so this allows to mock only class methods. The main point to understand here is that the class mock and the instance mock are not related like the class and the instance they are mocking. Both have the same Mock
type, and the instance mock doesn't know the class mock that has created it.
This means that instance method attributes have always to be set on the instance mock, as you have done in your second example. Using return_value
is eqivalent to that, so you could also write:
sagemaker_session.return_value.describe_training_job = MagicMock(return_value=self.mock_return_value)
Upvotes: 3