I am writing a unittest. How can I patch self.conf in the init method in class MyValidator? In my unittest, I want to create a fake self.conf and get the response to make assertion of each element in self.conf.
class MyValidator(wsgi.Middleware):
def __init__(self, app):
self.app = app
self.conf = {
'auth_uri': CONF.someuri
'admin_domain_name': CONF.somedomainname,
'admin_user': CONF.someuser,
'admin_password': CONF.get_admin_password(),
'domain_name': CONF.somedomainname
}
For unittest, I am thinking to do.. (I know this is wrong.. but you get the idea)
#mock.patch('my_module.MyValidator.__init__.conf')
def setUp(self, mock_config):
#webob.dec.wsgify()
def fake_app(req):
return webob.Response()
self.request = webob.Request.blank('/')
mock_config = {
'auth_uri': 'testuri'
....
....
}
self.middleware = MyValidator(fake_app)
def test_auth_uri(self):
auth_uri = 'testuri'
env_auth_uri = self.request.environ.get('auth_uri', None)
self.assertEqual(auth_uri, env_auth_uri)
What should be done to patch self.conf to get intended response?
Even I'm using mocking and patching extensively I don't think your case need it. conf is a MyValidator's public attribute and you don't need anything more than change it as you need.
def setUp(self):
#webob.dec.wsgify()
def fake_app(req):
return webob.Response()
self.request = webob.Request.blank('/')
self.middleware = MyValidator(fake_app)
self.middleware.conf = {
'auth_uri': 'testuri'
....
....
}
In this case patch cannot give to you nothing more because you are not interested about dict access and some smaller context where changes are made. If in some other test you need some other values you can use either self.middleware.conf.update(...) or self.middleware.conf[...]=... without change the behavior of other tests because setUp() configure it in the same way for every tests.
Things become different if conf is a read only property of MyValidator (a little bit better design). In this case you need to patch it to make your test:
class MyValidator(wsgi.Middleware):
def __init__(self, app):
self.app = app
#property
def conf(self):
return {
'auth_uri': CONF.someuri
'admin_domain_name': CONF.somedomainname,
'admin_user': CONF.someuser,
'admin_password': CONF.get_admin_password(),
'domain_name': CONF.somedomainname
}
Where the test class should be
#mock.patch('my_module.MyValidator.conf', new_callable=PropertyMock)
def setUp(self, mock_conf):
#webob.dec.wsgify()
def fake_app(req):
return webob.Response()
self.request = webob.Request.blank('/')
mock_conf.return_value = {
'auth_uri': 'testuri'
....
....
}
self.middleware = MyValidator(fake_app)
def test_auth_uri(self):
auth_uri = 'testuri'
env_auth_uri = self.request.environ.get('auth_uri', None)
self.assertEqual(auth_uri, env_auth_uri)
Patch __init__() is useful in rare cases. For instance when __init__ do some work and need resources that cannot be used or will not be used in test environment. An example of it is when init try to access to databases, use network resources, start new thread and so on.
Related
I am new to pytest.
I am trying to mock/replace my client.py with fake_client.py for the testing.
The fake_client class contains the same methods as the client class.
Here is my project structure and code:-
abc/base_class.py
from .client import Client
class PushBase:
def __init__(self, hostname):
self.client = Client(hostname)
def process_product(self, item): # item type is dict {}
product_id = item.get('product_id')
if item.get('state') == 'present':
if not product_id:
# creating product
product_id = self.client.create_product(item.get('data'))
return product_id
# update product
self.client.update_product(item.get('data'))
elif item.get('state') == 'absent':
# delete product
self.client.delete_product(product_id)
This is my client.py with API calls
in abc/client.py
class Client:
def __init__(self, hostname):
self.hostname = hostname
# some other stuff
def create_product(self, params=None):
# some code
def update_product(self, params=None):
# some code
def delete_product(self, params=None):
# some code
I have created a fake client to test against the actual client.py
and it has the same methods as the client.py
in tests/fake_client.py
class FakeClient:
def __init__(self, *args, **kwargs):
pass
def create_product(self):
# some code
def update_product(self):
# some code
def delete_product(self):
# some code
in tests/test_base_class.py
from tests.fake_client import FakeClient
import unittest
from abc.base_class import BaseClass
import pytest
try:
import mock
except ImportError:
from unittest import mock
class TestBaseClassOperations(unittest.TestCase):
def setUp(self):
self.push_base = BaseClass("http://fake_host_nmae/test", "foo", "bar")
self.push_base.client = mock.patch('abc.base_class.Client', new=FakeClient()).start()
def test_create_valid_product(self):
product_dict = { # some stuff }
created_product_id = self.push_base.process_product(product_dict)
# process_product() will call create_product from fake client
# will return 1 if FakeClient().create_product() called
assert created_product_id == 1
I tried it another way.
#pytest.fixture
def fixture_product_creation():
return { # product fixture
}
#mock.patch('abc.base_class.Client', return_value=FakeClient())
class TestBaseClassAgain:
def test_create_valid_product(self, mock_client, fixture_product_creation):
push_base = BaseClass("http://fake_host_nmae/test", "foo", "bar")
created_product_id = push_base.process_product(fixture_product_creation)
expected = 1
assert created_product_id == expected
# how can I use this mock_client here?
Although I can replace the client with the FakeClient, but I am unsure how to arrange all the mock things to get it tested with the assert or assert_called_with calls.
I referred this but not able to arrange it in a proper pythonic way.
Can anyone help me rearrange this and suggest to me any better way to replace the client class with the fake client class by using pytest mock?
Thanks.
In order to properly test this you should make a change to your PushBase class. It is not good practice to instantiate a dependent object in the __init__ method of your class, instead consider passing the object in. This makes testing easier as you can just inject the dependency as needed. Another option would be to make a #classmethod that instantiates the object with the client. In the code below I illustrate how to do the former.
It also appears you have an indentation error as the update_product method can never be called based on the logic you currently have.
# base.py
class PushBase:
def __init__(self, client):
self.client = client
def process_product(self, item): # item type is dict {}
product_id = item.get('product_id')
if item.get('state') == 'present':
if not product_id:
# creating product
product_id = self.client.create_product(item.get('data'))
return product_id
# update product
self.client.update_product(item.get('data'))
elif item.get('state') == 'absent':
# delete product
self.client.delete_product(product_id)
# test_base.py
import pytest
from src.base import PushBase
def test_create_product_id(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "present",
"data": "fizz"
}
mock_client.create_product.return_value = "ok"
product_id = base.process_product(item)
assert product_id == "ok"
mock_client.create_product.assert_called_once_with("fizz")
def test_update_product(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "present",
"data": "bang",
"product_id": "baz"
}
base.process_product(item)
mock_client.update_product.assert_called_once_with("bang")
def test_delete_product(mocker):
mock_client = mocker.MagicMock()
base = PushBase(mock_client)
item = {
"state": "absent",
"product_id": "vroom"
}
base.process_product(item)
mock_client.delete_product.assert_called_once_with("vroom")
============================================== test session starts ===============================================
platform darwin -- Python 3.8.9, pytest-7.0.1, pluggy-1.0.0
rootdir: ***
plugins: asyncio-0.18.3, hypothesis-6.48.1, mock-3.7.0
asyncio: mode=strict
collected 3 items
tests/test_base.py ...
=============================================== 3 passed in 0.01s ================================================
I am using the pytest-mock package, which is where the mocker fixture comes from. The nice thing about being able to inject a dependency into your class is you don't need to configure all the methods beforehand, you can modify what you need within each test function. There are improvements you can make to the tests above, but that exercise is left to you. Hopefully this should help you understand the direction you should go in.
my project has a file called config.py which has, among others, the following code:
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_A = 'name_of_secret_database_A'
def secret(self):
if self.value:
return get_secret(self.value)
return {}
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
I need to somehow mock get_secret in tests with pytest for all enum calls, for example Secret.DATABASE_A.secret ()
You can use monkeypatch to override the behaviour of get_secret(). I have made the get_secret() method a static method of the Secret class, but you can make it part of any module you want and import it as well. Just make sure you change in in the monkeypatch.setattr() call as well.
import pytest
from enum import Enum
class Secret(Enum):
DATABASE_A = 'name_of_secret_database_A'
DATABASE_B = 'name_of_secret_database_B'
def secret(self):
if self.value:
return Secret.get_secret(self.value)
return {}
#staticmethod
def get_secret(secret_name):
session = Session()
client = session.client(
service_name='secretsmanager',
region_name='us-east-1',
)
secret_value = client.get_secret_value(SecretId=secret_name)
return loads(secret_value.get('SecretString', "{}"))
def test_secret_method(monkeypatch):
def get_secret(secret_name):
return "supersecret"
monkeypatch.setattr(Secret, "get_secret", get_secret)
s = Secret.DATABASE_A
assert s.secret() == "supersecret"
This returns into 1 passed test.
What is happening here is, that I created a function get_secret() in my test_secret_method as well, and then overwrite the Secret.get_secret() with that new method. Now, you can use the Secret class in your test_method and be sure what the 'get_secret()' method will return without actually running the original code.
When I patch a method directly using unittest.mock.patch, I can use the mock.assert_called_with() function to correctly assert whether that method was called with certain values. However, when I patch a class directly, any asserts I use on the class itself or any of the class methods come back with "AssertionError: Not Called" even though the class and methods are called.
There are examples in the unittest documentation of successfully patching a class and using assertions: unittest mock doc
Here is my test with the SiteMetaData class methods patched directly and EdgeAPIRoutes class patched:
Test
#patch("repo.routes.EdgeAPIRoutes")
#patch("repo.site_meta_data.SiteMetaData.get_repo_type")
#patch("repo.site_meta_data.SiteMetaData.get_edge_api_inputs")
def test_get_route_list_repo_interface_edge(self, mock_edge_api_inputs,
mock_get_repo_type, mock_edge_api_routes_class):
"""
Test that SiteMetaData.get_route_list is used
appropriately and returns expected values from
mock as a valid response object
"""
from usecase import interface
from usecase import request_objects
from usecase.response_objects import ResponseSuccess
from utils.utils import DatetimeString
all_filters = {"filters" : {"parent_company" : "parent_1",
"site" : "site_1",
"start_date" : DatetimeString("2020-01-01 00:00:00.000"),
"end_date" : DatetimeString("2020-05-01 00:00:00.000")
}
}
example_route_list = [
"route_1",
"route_2",
"route_3"
]
edge_inputs = {
"edge_site" : "edge_site_name",
"edge_key" : "ffff-ffff-ffff"
}
error_response = None
mock_edge_api_inputs.return_value = [
edge_inputs["edge_site"],
edge_inputs["edge_key"],
error_response
]
mock_get_repo_type.return_value = [
"Minestar Edge",
error_response
]
mock_edge_api_routes_class.get_route_list.return_value = [
example_route_list,
error_response
]
resp = interface.get_route_list(request=request_objects.RouteList.request_wfilters(all_filters))
mock_get_repo_type.assert_called_with(all_filters["filters"]["parent_company"],
all_filters["filters"]["site"])
mock_edge_api_inputs.assert_called_with(all_filters["filters"]["parent_company"],
all_filters["filters"]["site"])
mock_edge_api_routes_class.get_route_list.assert_called_with(all_filters["filters"]["start_date"],
all_filters["filters"]["end_date"])
mock_edge_api_routes_class.assert_called_once_with(edge_inputs["edge_site"], edge_inputs["edge_key"])
self.assertTrue(bool(resp))
self.assertEqual(resp.type_, ResponseSuccess.SUCCESS)
self.assertEqual(resp.value["routes"], example_route_list)
Method
def get_route_list(request):
"""
"""
interfacelog.info("running get_route_list")
if bool(request):
repo_type, repo_type_error = site_meta_data.get_repo_type(request.filters["parent_company"],
request.filters["site"])
if repo_type_error is not None:
'''
handle any get repo type system errors
'''
pass
if repo_type == "Minestar Edge":
edge_site, edge_key, edge_error = site_meta_data.get_edge_api_inputs(request.filters["parent_company"],
request.filters["site"])
ear = EdgeAPIRoutes(edge_site, edge_key)
rl, routes_error = ear.get_route_list(request.filters["start_date"],
request.filters["end_date"])
if routes_error is not None:
'''
handle any get routes system error
'''
pass
success_resp = response_objects.ResponseSuccess()
success_resp.value = {"routes" : rl}
return success_resp
else:
'''
handle and errors due to an invalid request
'''
return response_objects.ResponseFailure.build_from_invalid_request_object(request)
Result
The assertions for the SiteMetaData patched methods pass while the assertions for the EdgeAPIRoutes class and methods fail. Based on the documentation, its seems both means of patching should have their assertions passing.
As #MrBeanBremen pointed out in his comment, where the classes are being patched is key. In my question, I was patching them at the source, but since they were imported into my interface.py file, they need to be patched in that location.
Also, since SiteMetaData is instantiated at the global level, its instance needs to be patched rather than the class itself. Since, EdgeAPIRoutes is instantiated within the get_route_list function, the class itself needs to be patched. Then to assign return values to the EdgeAPIRoutes's instance methods, the return_value of the patched class needs to be used:
mock_edge_api_routes_class.return_value.get_route_list.return_value = [
example_route_list,
error_response
]
Since the site_meta_data instance is patched directly, the instance method values can be assigned directly in the patch:
mock_site_meta_data.get_edge_api_inputs.return_value = [
edge_inputs["edge_site"],
edge_inputs["edge_key"],
error_response
]
interface.py
import logging
from repo.site_meta_data import SiteMetaData
from repo.routes import EdgeAPIRoutes
from usecase import response_objects
interfacelog = logging.getLogger(__name__)
site_meta_data = SiteMetaData()
def get_route_list(request):
"""
"""
interfacelog.info("running get_route_list")
if bool(request):
repo_type, repo_type_error = site_meta_data.get_repo_type(request.filters["parent_company"],
request.filters["site"])
if repo_type_error is not None:
'''
handle any get repo type system errors
'''
pass
if repo_type == "Minestar Edge":
edge_site, edge_key, edge_error = site_meta_data.get_edge_api_inputs(request.filters["parent_company"],
request.filters["site"])
ear = EdgeAPIRoutes(edge_site, edge_key)
rl, routes_error = ear.get_route_list(request.filters["start_date"],
request.filters["end_date"])
if routes_error is not None:
'''
handle any get routes system error
'''
pass
success_resp = response_objects.ResponseSuccess()
success_resp.value = {"routes" : rl}
return success_resp
else:
'''
handle and errors due to an invalid request
'''
return response_objects.ResponseFailure.build_from_invalid_request_object(request)
test_interface function patch setup
#patch("usecase.interface.EdgeAPIRoutes")
#patch("usecase.interface.site_meta_data")
def test_get_route_list_repo_interface_edge(self, mock_site_meta_data,
mock_edge_api_routes_class):
"""
Test that SiteMetaData.get_route_list is used
appropriately and returns expected values from
mock as a valid response object
"""
...
I wanted to create a proper post_create (also post_get and post_put) hooks, similar to the ones I had on the DB version of my app.
Unfortunately I can't use has_complete_key.
The problem is quite known: lack of is_saved in a model.
Right now I have implemented it like this:
class NdbStuff(HooksInterface):
def __init__(self, *args, **kwds):
super(NdbStuff, self).__init__(*args, **kwds)
self._is_saved = False
def _put_async(self, post_hooks=True, **ctx_options):
""" Implementation of pre/post create hooks. """
if not self._is_saved:
self._pre_create_hook()
fut = super(NdbStuff, self)._put_async(**ctx_options)
if not self._is_saved:
fut._immediate_callbacks.insert(
0,
(
self._post_create_hook,
[fut],
{},
)
)
self._is_saved = True
if post_hooks is False:
fut._immediate_callbacks = []
return fut
put_async = _put_async
#classmethod
def _post_get_hook(cls, key, future):
obj = future.get_result()
if obj is not None:
obj._is_saved = True
cls._post_get(key, future)
def _post_put_hook(self, future):
if future.state == future.FINISHING:
self._is_saved = True
else:
self._is_saved = False
self._post_put(future)
Everything except the post_create hook seems to work.
The post_create is triggered every time the I use put_async without retrieving the object first.
I would really appreciate a clue on how to trigger the post_create_hook only once after the object was created.
I am not sure why you are creating the NDBStuff class.
Any way if you creating an instance of a class, and you want to track _is_saved or something similar , use a factory to control creation and setting of the property, in this case it makes more sense to track _is_new for example.
class MyModel(ndb.Model):
some_prop = ndb.StringProperty()
def _pre_put_hook(self):
if getattr(self,'_is_new',None):
self._pre_create_hook()
# do something
def _pre_create_hook(self):
# do something on first save
log.info("First put for this object")
def _post_create_hook(self, future):
# do something
def _post_put_hook(self, future);
if getattr(self,'_is_new', None):
self._post_create_hook(future)
# Get rid of the flag on successful put,
# in case you make some changes and save again.
delattr(self,'_is_new')
#classmethod
def factory(cls,*args,**kwargs):
new_obj = cls(*args,**kwargs)
settattr(new_obj,'_is_new',True)
return new_obj
Then
myobj = MyModel.factory(someargs)
myobj.put()
myobj.some_prop = 'test'
myobj.put()
Will call the _pre_create_hook on the first put, and not on the second.
Always create the entities through the factory then you will always have the to call to _pre_create_hook executed.
Does that make sense ?
i'd like to use cherrypy but i don't want to use the normal dispatcher, i'd like to have a function that catch all the requests and then perform my code. I think that i have to implement my own dispatcher but i can't find any valid example. Can you help me by posting some code or link ?
Thanks
make a default function:
import cherrypy
class server(object):
#cherrypy.expose
def default(self,*args,**kwargs):
return "It works!"
cherrypy.quickstart(server())
What you ask can be done with routes and defining a custom dispatcher
http://tools.cherrypy.org/wiki/RoutesUrlGeneration
Something like the following. Note the class instantiation assigned to a variable that is used as the controller for all routes, otherwise you will get multiple instances of your class. This differs from the example in the link, but I think is more what you want.
class Root:
def index(self):
<cherrpy stuff>
return some_variable
dispatcher = None
root = Root()
def setup_routes():
d = cherrypy.dispatch.RoutesDispatcher()
d.connect('blog', 'myblog/:entry_id/:action', controller=root)
d.connect('main', ':action', controller=root)
dispatcher = d
return dispatcher
conf = {'/': {'request.dispatch': setup_routes()}}
Hope that helps : )
Here's a quick example for CherryPy 3.2:
from cherrypy._cpdispatch import LateParamPageHandler
class SingletonDispatcher(object):
def __init__(self, func):
self.func = func
def set_config(self, path_info):
# Get config for the root object/path.
request = cherrypy.serving.request
request.config = base = cherrypy.config.copy()
curpath = ""
def merge(nodeconf):
if 'tools.staticdir.dir' in nodeconf:
nodeconf['tools.staticdir.section'] = curpath or "/"
base.update(nodeconf)
# Mix in values from app.config.
app = request.app
if "/" in app.config:
merge(app.config["/"])
for segment in path_info.split("/")[:-1]:
curpath = "/".join((curpath, segment))
if curpath in app.config:
merge(app.config[curpath])
def __call__(self, path_info):
"""Set handler and config for the current request."""
self.set_config(path_info)
# Decode any leftover %2F in the virtual_path atoms.
vpath = [x.replace("%2F", "/") for x in path_info.split("/") if x]
cherrypy.request.handler = LateParamPageHandler(self.func, *vpath)
Then just set it in config for the paths you intend:
[/single]
request.dispatch = myapp.SingletonDispatcher(myapp.dispatch_func)
...where "dispatch_func" is your "function that catches all the requests". It will be passed any path segments as positional arguments, and any querystring as keyword arguments.