I'm trying to make a mock of a class with unittest.mock.
I have the following structure of packages and modules:
services
service_one.py
repository
repository_mongodb.py
test
services
test_service_one.py
The class repository_mongodb.py is used inside the class service_one.py (by importing the class).
This is the code for the files.
File repository_mongodb.py
class RepositoryMongoDB:
def __init__(self):
self.library = []
def save(self, thing):
self.library.append(thing)
return True
File service_one.py
from repository.RepositoryBookList import RepositoryBookList
class ServiceRodri:
def __init__(self):
self.repository = RepositoryMongoDB()
def save_service(self, thing):
# Do validation or something else
return self.repository.save(thing)
Now I want to try to make a mock of the class ServiceRodri, and here's what I do.
import unittest
from unittest.mock import patch
from service.service_one import ServiceOne
class ServiceOneTest(unittest.TestCase):
def setUp(self):
self.service_one = ServiceOne()
#patch('service.service_one.RepositoryMongoBD')
def test_get_one_book_if_exists_decorator(self, mock_repo):
mock_repo.save.return_value = "call mock"
result = self.serviceRodri.save_service("")
self.assertEquals("call mock", result)
I want that when I call the method "save" of the class RepositoryMongoBD to return the result I'm assigned to it. But this doesn't happen.
I've also tried to do it this way.
#patch('repository.repository_mongodb.RepositoryMongoDB')
def test_get_one_book_if_exists_decorator(self, mock_repo):
mock_repo.save.return_value = "call mock"
result = self.serviceRodri.save_service("")
self.assertEquals("call mock", result)
But it doesn't work either.
But if I try to mock the function save() this way.
#patch('service.service_one.RepositoryMongoDB.save')
def test_get_one_book_if_exists_decorator_2(self, mock_repo):
mock_repo.return_value = "call mock"
result = self.serviceRodri.save_service("")
self.assertEquals("call mock", result)
Works correctly!!! I understand what it's doing is when the call save() is found in service_one module, it is replaced by the mock.
What would be the right way to do it? (and the best way)
I'm a begginer in the world of python. I have searched and read many post, but all the examples shown are very easy (like sum() method). I've tested in other languages but never in Python.
If you insist on using patch, your final attempt is the right way.
The reason the previous attempts don't work is because the patch is applied after the import, so you're not patching the object you think you are.
I've been working with patch for a long time and this still bites me occasionally. So I recommended using simple constructor based dependency injection.
In service_one
class ServiceOne:
def __init__(self, respository):
self.repository
Initialize this with service_rodri = ServiceRodri(RepositoryMongoDB()), maybe in the __init__ file or something. Then in your test, you can create this mock in your setup.
class ServiceOneTest(unittest.TestCase):
def setUp(self):
self.repository = MagicMock()
self.service_one = ServiceOne(self.repository)
N.B. patching vs. dependency injection:
Patching will also couple the tests to the import structure of your program. This makes safe refactoring that alters the structure of your modules strictly more difficult. It's best used with legacy code when you need to get some tests in place before making changes.
Related
In my application I have a class that is called from a flask service. This class takes some attributes from the flask.request object, so I want to mock them.
An example of the implementation that I have is:
myClassHelper.py
from flask import request
class MyClassHelper:
def __init__(self, addRequestData=False):
self.attribute = 'something'
self.path = request.path if addRequestData else None
def __str__(self):
return 'attribute={0}; path={1};'.format(self.attribute, self.path)
myClassHelperTest.py
from unittest import TestCase
from unittest.mock import MagicMock
import flask
from myClassHelper import MyClassHelper
class MyClassHelperTest(TestCase):
def setUp(self):
self.path = '/path'
self.unmock = {}
self.unmock['flask.request'] = flask.request
flask.request = MagicMock(path='/path')
def tearDown(self):
flask.request = self.unmock['flask.request']
def test_printAttributes(self):
expectedResult = 'attribute=something; path={0};'.format(self.path)
result = str(MyClassHelper(addRequestData=True))
self.assertEqual(expectedResult, result)
The problem comes when I do the import from myClassHelper import MyClassHelper. This goes to the import from flask import request inside MyClassHelper. So the mock in the setUp method of the test class, it's not being applied.
This can be solved by just importing flask and accessing to the path attribute like flask.request.path. But I would like to avoid importing the full flask module.
Is there any way to create a unit test for a method that uses attributes from flask.request, mocking them and without using the flask test client?
There must be a way but unit testing code like this is going to cause you troubles anyway. The SUT is accessing global state that is managed by another module, thus your tests need to properly set up that global state. This can be done either by using that another module as is, which you don't want for good reasons (plus it wouldn't be unit testing anymore), or by monkey-patching it. This is often tricky (as you already found out) and brittle (your tests will break if you change the way you import things in the production code; why should that happen if the relevant behavior has not changed?)
The fix for this kind of problems is making your objects ask for the things they need instead of looking for them in global state. So if all an instance of MyClassHelper needs is a path, just make it ask for a path. Let the calling code figure out where the path should come from. Specifically your tests can easily provide canned paths.
This is how your test would look if you follow this principle:
class MyClassHelperTest(TestCase):
def test_printAttributes(self):
expectedResult = 'attribute=something; path=/path;'
result = str(MyClassHelper('/path'))
self.assertEqual(expectedResult, result)
Much simpler than before. And this is how you make it pass:
class MyClassHelper:
def __init__(self, path):
self.attribute = 'something'
self.path = path
def __str__(self):
return 'attribute={0}; path={1};'.format(self.attribute, self.path)
You do not really need attribute if the behavior in the test is all you want. I left it there in order to deviate less from your original code. I assume you have other tests that show why it is actually needed.
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()
I've searched for hours. Can't find anyone even trying to do this. Hmmm.
I believe I have to override a single method within a class instance. I do not mean patch(return_value=). I need to make the method in question do something involving self.
I'll try to break it down. Liberally paraphrasing and including one of the many things I tried, which doesn't work ...
class SetupClass(object):
def set_some_stuff(self):
data_list = functon_cannot_be_run_on_test_platform()
self.something = data_list[0]
self.something_else = data_list[1]
class UUT(object):
self.config = SetupClass()
assert self.config.something == 'foo'
class UnitTests(TestCase):
#patch('SetupClass')
def test_UUT(self, mock1):
def WedgeClass(SetupClass):
def set_some_stuff(self):
self.something = 'foo'
pass # I'm a Python newbie, in too deep
wedge_class = WedgeClass()
mock1.return_value = wedge_class # doesn't work. context errors
uut = UUT() # <-- would crash here, because assert above
Assume that I cannot make changes to UUT or SetupClass.
Testing cannot even get off the ground because the assertion will fail, due to, SetupClass.functon_cannot_be_run_on_test_platform(). Note that simply mocking SetupClass.functon_cannot_be_run_on_test_platform will not solve the problem, because reasons.
ATM, I figure the only way to get around this mess is to somehow override SetupClass.set_some_stuff. I cannot simply mock the entire class, because UUT relies heavily on its other functionality as well. I need everything to work as is, except this one method and I need that method to be able to access, self in the same context as originally intend.
I tried various things involving subclassing and mock.return_value etc. I'd rather not recall the pain that caused. :p
My kingdom for test-driven code in the first place! This code contains a convolution of co-dependencies. :-/
Apart from the multiple errors in the example code I wrote up off the top of my head at the cafe ...
The problem was (mostly) that I was using return_value instead of side_effect to 'replace' the patched class with my own subclass.
My need, re-stated perhaps more clearly now, is to override a single method, set_some_stuff, in a class within the unit under test (UUT) but without mocking the class.
The method issues several 'self.foo = bar' statements, which I want to change for testing purposes. Thus, mocking the method's (unused) return value is not enough ... and patch.object seems to lose class context, "'self' unknown" or the like.
Finally, here is the working code, doing what I need ...
import unittest
import mock
class SetupClass(object):
def set_some_stuff(self):
data_list = ['data not available', 'on test_platform'] # CRASH!
self.something = data_list[0]
self.something_else = data_list[1]
class UUT:
def __init__(self):
self.config = SetupClass()
self.config.set_some_stuff()
assert self.config.something == 'foo' # <-- used to crash before here ...
self.got_here = True # ... but now the override method from
# WedgeClass is being used! :=)
"""
Subclass the original class, overriding just the method in question. Then set
the subclass as the side_effect of the patched original, effectively replacing it.
"""
class WedgeClass(SetupClass):
def set_some_stuff(self):
self.something = 'foo'
pass # I'm a Python newbie, in too deep
class UnitTests(unittest.TestCase):
#mock.patch(__module__+'.SetupClass')
def test_UUT(self, mock1):
wedge_class = WedgeClass()
mock1.side_effect = WedgeClass # <--- Ureka! 'side_effect' not 'return_value' was the ley!
uut = UUT()
self.assertTrue(uut.got_here)
(Nota bene: This is heavily modified from the original question, to include details I erroneously elided.)
This is the (summarized) file (common.py) I'm testing. It contains a decorator (derived from the Decorum library) that calls a class method on another object(A): I want to patch out A, because that code makes an external call I'm not testing.
from decorum import Decorum
class A:
#classmethod
def c(cls):
pass
class ClassyDecorum(Decorum):
"""Hack to allow decorated instance methods of a class object to run with decorators.
Replace this once Decorum 1.0.4+ comes out.
"""
def __get__(self, instance, owner):
from functools import partial
return partial(self.call, instance)
class B(Decorum):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def init(self, *args, **kwargs):
A.c()
return super().init(*args, **kwargs)
I'd like to #patch class A in my unittest, to isolate and check B.d()'s functionality. This is my unittest (located in test/test_common.py):
class BDecoratedClass(MagicMock):
#B
def dummy_func(self):
return "Success"
class TestB(TestCase):
#patch('unittest_experiment.A', autospec=True)
def test_d(self, mock_a):
b = BDecoratedClass()
b.dummy_func()
mock_a.c.assert_called_once_with() # Fails
Debugging the above, I see that A is never actually mocked: the code proceeds into A's code, so it makes sense that mock_a is never called, and thus the assertion fails. However, I'd like to properly monkey patch A. This approach works if I'm monkey patching an import that exists in common.py, but apparently not if the class is defined there?
Note that I think this is likely an issue of where I'm patching, that is #patch('common.A', autospec=True) should more likely be something like #patch('where.python.actually.finds.A.when.B.calls.A', autospec=True). But I'm very unclear on how to determine if that is the case, and if so, what the correct path is. For instance, #patch('BDecorated.common.A', autospec=True) does not work.
Thanks to #user2357112, I arrived at this solution. Caveat: I don't know if this is standard or 'best' practice, but it seems to work.
First, move the BDecoratedClass to it's own file in test/dummy.py. Then change the test to this:
class TestB(TestCase):
#patch('common.A', autospec=True)
def test_d(self, mock_a):
from test.dummy import BDecoratedClass
b = BDecoratedClass()
b.dummy_func()
mock_a.c.assert_called_once_with() # Succeeds
This forces the patch to execute prior to the import of the dummy class being decorated. It's a little weird because the import is inside the function, but for a test that seems fine.
Bigger Caveat:
This only works for the first test that imports something from the module where, in this case BDecoratedClass imports from. At that juncture everything else in the class has been loaded and cannot be patched.
It looks like patch substitutes the import and that's why it's too late at that point unless you work around it like you explained in your the answer. I found that using patch.object for individual methods also work. So something like:
class TestB(TestCase):
#patch.object(A, 'c')
def test_d(self, mock_c):
b = BDecoratedClass()
b.dummy_func()
mock_c.assert_called_once_with() # Succeeds
I have a group of test cases that all should have exactly the same test done, along the lines of "Does method x return the name of an existing file?"
I thought that the best way to do it would be a base class deriving from TestCase that they all share, and simply add the test to that class. Unfortunately, the testing framework still tries to run the test for the base class, where it doesn't make sense.
class SharedTest(TestCase):
def x(self):
...do test...
class OneTestCase(SharedTest):
...my tests are performed, and 'SharedTest.x()'...
I tried to hack in a check to simply skip the test if it's called on an object of the base class rather than a derived class like this:
class SharedTest(TestCase):
def x(self):
if type(self) != type(SharedTest()):
...do test...
else:
pass
but got this error:
ValueError: no such test method in <class 'tests.SharedTest'>: runTest
First, I'd like any elegant suggestions for doing this. Second, though I don't really want to use the type() hack, I would like to understand why it's not working.
You could use a mixin by taking advantage that the test runner only runs tests inheriting from unittest.TestCase (which Django's TestCase inherits from.) For example:
class SharedTestMixin(object):
# This class will not be executed by the test runner (it inherits from object, not unittest.TestCase.
# If it did, assertEquals would fail , as it is not a method that exists in `object`
def test_common(self):
self.assertEquals(1, 1)
class TestOne(TestCase, SharedTestMixin):
def test_something(self):
pass
# test_common is also run
class TestTwo(TestCase, SharedTestMixin):
def test_another_thing(self):
pass
# test_common is also run
For more information on why this works do a search for python method resolution order and multiple inheritance.
I faced a similar problem. I couldn't prevent the test method in the base class being executed but I ensured that it did not exercise any actual code. I did this by checking for an attribute and returning immediately if it was set. This attribute was only set for the Base class and hence the tests ran everywhere else but the base class.
class SharedTest(TestCase):
def setUp(self):
self.do_not_run = True
def test_foo(self):
if getattr(self, 'do_not_run', False):
return
# Rest of the test body.
class OneTestCase(SharedTest):
def setUp(self):
super(OneTestCase, self).setUp()
self.do_not_run = False
This is a bit of a hack. There is probably a better way to do this but I am not sure how.
Update
As sdolan says a mixin is the right way. Why didn't I see that before?
Update 2
(After reading comments) It would be nice if (1) the superclass method could avoid the hackish if getattr(self, 'do_not_run', False): check; (2) if the number of tests were counted accurately.
There is a possible way to do this. Django picks up and executes all test classes in tests, be it tests.py or a package with that name. If the test superclass is declared outside the tests module then this won't happen. It can still be inherited by test classes. For instance SharedTest can be located in app.utils and then used by the test cases. This would be a cleaner version of the above solution.
# module app.utils.test
class SharedTest(TestCase):
def test_foo(self):
# Rest of the test body.
# module app.tests
from app.utils import test
class OneTestCase(test.SharedTest):
...