Python: Mocking an instance of a class inside a method using #patch - python

I have a method foo in Python that creates a Service class. I want to mock the Service class but when I run the test, it still attempts to instantiate the class. Here is the simplified version of my setup:
class Service:
def __init__(self, service):
self.service_stuff = service
def run_service(self):
do_service_stuff()
def foo:
new_service = Service("bar")
new_service.run_service()
Then my unit test:
#patch('mymodule.service_file.Service')
def test_foo(self, mock_service):
foo()
I would like to run foo, but have it use my mocked object instead of creating an actual Service instance, but instead, when I run it, it tries to instantiate an actual instance of Service() and runs foo() as usual even though it seems to recognize the string signature I've put into patch. Why is this happening?

Figured it out: The patch reference to the class had to be of its imported name in the method itself rather than the original class, similar to https://stackoverflow.com/a/32461515/4234853
So the patch should look like:
#patch('mymodule.foo_file.Service')
instead of trying to patch the class directly.

In this case, it might be easier to make your function more test-friendly:
def foo(cls=Service):
new_service = cls("bar")
new_service.run_service()
Then your test doesn't need to patch anything.
def test_foo(self):
mock_service = Mock()
foo(mock_service)

Related

Mock a method of a specific class instance in pytest tests

I can't figure out how to mock the method of a specific instance of a class.
I have the following class instances in my code
myInstance = MyCustomClass()
myInstanceToMock = MyCustomClass()
In a test, I would like to mock a method of the second one but not the first.
I can manage to mock the method for both by doing something like that:
import pytest
def the_mocked_method():
pass
def test_something(mocker):
mocker.patch.object(MyCustomClass, "the_method", new=the_mocked_method)
The problem is that both myInstance and myCustomInstance are affected and that doesn't fit my usecase.
Any way to mock only a method of a specific existing instance ?

How do I mock a method on an object created by an #patch decorator?

I'm trying to use #patch decorators and mock objects to test the behavior of my code consuming some libraries. Unfortunately, I can't see any way to do what I'm trying to do using only decorators, and it seems like I should be able to. I realize that generally I should be more concerned with the return value of a method than the particular sequence of calls it makes to a library, but it's very difficult to write a test to compare one image to another.
I thought that maybe I could create instances of my mock objects and then use #patch.object(...) but that didn't work, and obviously #patch('MockObject.method') will only mock class methods. I can get the code to work by using with patch.object(someMock.return_value, 'method') as mockName but this makes my tests messy.
from some.module import SomeClass
class MockObject:
def method(self):
pass
class TestClass:
#patch('SomeClass.open', return_value=MockObject())
#patch('??????.method') # I can't find any value to put here that works
def test_as_desired(self, mockMethod, mockOpen):
instance = SomeClass.open()
instance.method()
mockOpen.assert_called_once()
mockMethod.assert_called_once()
#patch('SomeClass.open', return_value=MockObject())
def test_messy(self, mockOpen):
with patch.object(mockOpen.return_value, 'method') as mockMethod:
instance = SomeClass.open()
instance.method()
mockOpen.assert_called_once()
mockMethod.assert_called_once()
I think you are overcomplicating things:
import unittest.mock
#unittest.mock.patch.object(SomeClass, 'open', return_value=mock.Mock())
def test_as_desired(self, mock_open):
instance = SomeClass.open() # SomeClass.open is a mock, so its return value is too
instance.method()
mock_ppen.assert_called_once()
instance.method.assert_called_once()

pytest: how do I get the (mock) instances returned from a mocked class?

I must be tired, because surely there is an easy way to do this.
But I've read over the pytest docs and can't figure out this simple use case.
I have a little package I want to test:
class MyClass:
def __init__(self):
pass
def my_method(self, arg):
pass
def the_main_method():
m = MyClass()
m.my_method(123)
I would like to ensure that (1) an instance of MyClass is created, and that (2) my_method is called, with the proper arguments.
So here's my test:
from unittest.mock import patch
#patch('mypkg.MyClass', autospec=True)
def test_all(mocked_class):
# Call the real production code, with the class mocked.
import mypkg
mypkg.the_main_method()
# Ensure an instance of MyClass was created.
mocked_class.assert_called_once_with()
# But how do I ensure that "my_method" was called?
# I want something like mocked_class.get_returned_values() ...
I understand that each time the production code calls MyClass() the unittest framework whips up a new mocked instance.
But how do I get my hands on those instances?
I want to write something like:
the_instance.assert_called_once_with(123)
But where do I get the_instance from?
Well, to my surprise, there is only one mock instance created, no matter how many times you call the constructor (:
What I can write is:
mocked_class.return_value.my_method.assert_called_once_with(123)
The return_value does not represent one return value, though — it accumulates information for all created instances.
It's a rather abstruse approach, in my mind. I assume it was copied from some crazy Java mocking library (:
If you want to capture individual returned objects, you can use .side_effect to return whatever you want, and record it in your own list, etc.

Mock a class and a class method in python unit tests

I'm using python's unittest.mock to do some testing in a Django app. I want to check that a class is called, and that a method on its instance is also called.
For example, given this simplified example code:
# In project/app.py
def do_something():
obj = MyClass(name='bob')
return obj.my_method(num=10)
And this test to check what's happening:
# In tests/test_stuff.py
#patch('project.app.MyClass')
def test_it(self, my_class):
do_something()
my_class.assert_called_once_with(name='bob')
my_class.my_method.assert_called_once_with(num=10)
The test successfully says that my_class is called, but says my_class.my_method isn't called. I know I'm missing something - mocking a method on the mocked class? - but I'm not sure what or how to make it work.
Your second mock assertion needs to test that you are calling my_method on the instance, not on the class itself.
Call the mock object like this,
my_class().my_method.assert_called_once_with(num=10)
^^
A small refactoring suggestion for your unittests to help with other instance methods you might come across in your tests. Instead of mocking your class in each method, you can set this all up in the setUp method. That way, with the class mocked out and creating a mock object from that class, you can now simply use that object as many times as you want, testing all the methods in your class.
To help illustrate this, I put together the following example. Comments in-line:
class MyTest(unittest.TestCase):
def setUp(self):
# patch the class
self.patcher = patch('your_module.MyClass')
self.my_class = self.patcher.start()
# create your mock object
self.mock_stuff_obj = Mock()
# When your real class is called, return value will be the mock_obj
self.my_class.return_value = self.mock_stuff_obj
def test_it(self):
do_something()
# assert your stuff here
self.my_class.assert_called_once_with(name='bob')
self.mock_stuff_obj.my_method.assert_called_once_with(num=10)
# stop the patcher in the tearDown
def tearDown(self):
self.patcher.stop()
To provide some insight on how this is put together, inside the setUp method we will provide functionality to apply the patch across multiple methods as explained in the docs here.
The patching is done in these two lines:
# patch the class
self.patcher = patch('your_module.MyClass')
self.my_class = self.patcher.start()
Finally, the mock object is created here:
# create your mock object
self.mock_stuff_obj = Mock()
self.my_class.return_value = self.mock_stuff_obj
Now, all your test methods can simply use self.my_class and self.mock_stuff_obj in all your calls.
This line
my_class.my_method.assert_called_once_with(num=10)
will work if my_method is a class method.
Is it the case?
Otherwise, if my_method is just an normal instance method, then you will need to refactor the function do_something to get hold of the instance variable obj
e.g.
def do_something():
obj = MyClass(name='bob')
return obj, obj.my_method(num=10)
# In tests/test_stuff.py
#patch('project.app.MyClass')
def test_it(self, my_class):
obj, _ = do_something()
my_class.assert_called_once_with(name='bob')
obj.my_method.assert_called_once_with(num=10)

Is it possible to patch a class instance with an existing Singleton instance?

I have a class representing our DB layer which is being instantiated internally in some classes (I cannot pass it as an outside parameter)
For example:
class MyClass(object):
def __init__(self):
self.dbapi = DatabaseAPI()
self.timeout = 120
def some_methods(self):
pass
We're writing some Unit tests, and we'd like to mock the self.dbapi with an existing instance which we'll be creating before the test runs.
for example:
my_dbapi = DatabaseAPIMock()
...
...
#patch('MyModule.DatabaseAPI', my_dbapi)
def my_test(self):
my_class = MyClass() #<---This is where I'm not able to mock the DatabaseAPI
This is what I tried to achieve so far, but from debugging the code I see that the self.dbapi is instantiated with the real object and not with the pre-made mock.
What am I missing?
BTW, we're running python 2.7
Thanks in advance!
You're patching the wrong thing. You need to patch in the module that is assigning the attribute, ie the one that contains the target class.
It would be easier if you defined a method in your target class that gets your DatabaseAPI object. That way you can much more easily patch it.
class MyClass(object):
def __init__(self):
self.dbapi = self.get_db_api()
self.timeout = 120
def get_db_api():
return DatabaseAPI()
and the test becomes:
#patch('my_module.MyClass.get_db_api')
def my_test(self, my_method):
my_method.return_value = my_dbapi
Short answer: use
#patch('MyModule.DatabaseAPI', return_value=my_dbapi)
instead.
In MyModule DatabaseAPI is something like a factory but my_dbapi is a instance. So by set the instance as return value of your factory you mock self.dbapi reference.
If in production code you can change the design a little bit consider to move as #DanielRoseman's answer propose: it is a better design.

Categories

Resources