Mocking attributes using #patch decorator - python

I am struggling with mocking attributes in my Python tests. The function I am trying to test keeps failing because the mock probably returns the right value but is the wrong type (Should be string and it is a MagicMock instead.
I have found this answer and I understand I need to use a PropertyMock. But I can't get it to work neither with the context manager or using the #patch decorator. Mock attributes in Python mock?
Here is my test:
#patch('keys.views.requests.post')
#patch('keys.views.requests.Response.text', new_callable=PropertyMock)
def test_shows_message_when_receives_error(self, mock_response_text ,mock_post):
expected_error = escape(MESSAGE)
data_to_be_received = json.dumps({
"message":"Bad Request",
"errors":[{
"resource":"Application",
"field":"client_id",
"code":"invalid"
}]
})
mock_response_text.return_value = data_to_be_received
response = self.client.get('/users/tokenexchange?state=&code=abc123')
self.assertContains(response, expected_error)
And the code I am testing:
def token_exchange(request):
parameters = {'client_id': '##', 'client_secret': '##', 'code': code}
response = requests.post('https://www.strava.com/oauth/token', parameters)
data_received = json.loads(response.text)
if 'errors' not in data_received:
return HttpResponse(response.text)
else:
return render(request, 'home.html', {'error': STRAVA_AUTH_ERROR})
The error I keep getting is:
File "##", line 66, in token_exchange
data_received = json.loads(response.text)
TypeError: the JSON object must be str, bytes or bytearray, not 'MagicMock'
Thanks for your answers!!!

keys.views.requests.Response.text is highly likely a instance variable which cannot and should not be mocked using PorpertyMock
Here is quote from documentation:
Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class:
https://docs.python.org/2/tutorial/classes.html
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
How to mock a python class instance_variable?
I had a solution which copied from somewhere, it worked by too tedious:
Python mock a base class's attribute
In your specific case, mock Response class instead of the text instance
#patch('keys.views.requests.post')
#patch('keys.views.requests.Response')
def test_shows_message_when_receives_error(self, mock_response ,mock_post):
mock_post.return_value = None # mock the func
mock_response.text = mock.Mock(text=data_to_be_received).text

Related

Access self attributes without declaring self in class function

im working on a python package and I wonder how can I declare a class, which receive some attributes in the init function and then be able to use that 'self' attributes in the rest of the functions without declaring self as it's parameters.
Here is an example code to make it easier:
class API():
def __init__(self, token):
self.token = token
def info():
headers = {'Token': f'{self.token}'}
response = requests.post(some_url, headers=headers)
return response
I didn't put self in info() function because that function is going to be called from the outside, but it will be great be able to reuse the token attribute received in the class initialization. i don't know if I'm missing something so any suggestion will be much appreciated.
Edit
If I use my current code, I get an error because using self keyword without declaring it on the function class, but if I put it, then when I make the function call I can pass self argument.
self is not a keyword; it's just a conventional name for the instance of API that is passed to info when it is called as an instance method.
You can't call info without such an instance.
class API():
def __init__(self, token):
self.token = token
def info(self):
headers = {'Token': f'{self.token}'}
response = requests.post(some_url, headers=headers)
return response
a = API("some token")
a.info()
a.info() is roughly equivalent to API.info(a).

Unit testing a function which consumes request from django rest api view

I'm not sure what's the proper way to test functions which are used inside views/permission classes.
This is the payload of my request:
{"name": "John"}
And this is the function I want to test:
def get_name(request):
return request.data['name']
This is the view that will be using the function:
class SomeView(APIView):
def get(self, request):
name = get_name(request=request)
return Response(status=200)
How should I create a fixture to test the get_name function? I've tried this:
#pytest.fixture
def request_fixture()
factory = APIRequestFactory()
return factory.get(
path='',
data={"name": "John"},
format='json')
def test_get_name(request_fixture):
assert get_name(request=request_fixture) == "John"
But I'm getting an error:
AttributeError: 'WSGIRequest' object has no attribute data.
One workaround seems to be decoding the body attribute:
def get_name(request):
data = json.loads(request.body.decode('utf-8'))
return data['name']
But it doesn't feel like the right way to do this and I guess I'm missing something about the WSGIRequest class. Can someone explain to me how it should be tested? It would be great if I could use the same fixture to test the view too.
I don't think you need the test fixture. You aren't testing the whole view, just a helper function. You can make a request-like object very easily by adding a property to a lambda:
def test_get_name():
request = lambda: None
request.data = {"name": "John"}
assert get_name(request=request) == "John"

Mock instance created from class stored in variable

I have the following code:
class Messenger(object):
def __init__(self):
# Class Type of what messages will be created as.
message_class = Message
def publish(self, body):
# Instantiate object of type stored in `message_class`
message = message_class(body)
message.publish()
I want to assert that the Message.publish() method is called. How do I achieve this?
I've already tried the following ways:
Assign message_class to Mock or Mock(). If I debug what message_class(body) returns, it is a Mock, but I don't seem to be able to get the instance and assert it (because the Mock I assign in my test is not the instance used, it is the Type).
Patch Message class with decorator. Whenever I do this it seems like it does not catch it. When I debug what message_class(body) returns its of Message type, not Mock.
Try to mock the __init__ method of message_class in hopes that I can set the instance that is returned whenever the code tries to Instantiate the message. Does not work, throws errors because the __init__ method is not suppose to have a return value.
If you were storing the actual instance, I'd say you could do something like messenger.message.publish.assert_called_once, but since message_class is being stored, it makes it slightly trickier. Given that, you can pull the return_value from the mocked class and check the call that way. Here's how I did it:
Messenger. Note the slight modification to assign message_class to self. I'm assuming you meant to do that, otherwise it wouldn't work without some global funkiness:
'''messenger.py'''
class Message(object):
def __init__(self, body):
self.body = body
def publish(self):
print('message published: {}'.format(self.body))
class Messenger(object):
def __init__(self):
# Class Type of what messages will be created as.
self.message_class = Message
def publish(self, body):
# Instantiate object of type stored in `message_class`
message = self.message_class(body)
message.publish()
Test:
'''test_messenger.py'''
from unittest import mock, TestCase
from messenger import Messenger
class TestMessenger(TestCase):
#mock.patch('messenger.Message')
def test_publish(self, mock_message):
messenger = Messenger()
messenger.publish('test body')
# .return_value gives the mock instance, from there you can make your assertions
mock_message.return_value.publish.assert_called_once()

gRPC Python: Can't instantiate abstract class ServicerContext

I am trying to test the following method:
def my_method(self, request, context):
context.set_details('Already exists')
context.set_code(grpc.StatusCode.ALREADY_EXISTS)
To test it, I must pass in a request and a context (which is a grpc.ServicerContext object), like so:
import grcp
def test_my_method(self):
request = {"something": "something-else"}
context = grpc.ServicerContext()
my_method(request, context)
# Assert something here
The problem is, I get the following error when I run my tests:
TypeError: Can't instantiate abstract class ServicerContext with abstract methods add_callback, cancel, invocation_metadata, is_active, peer, send_initial_metadata, set_code, set_details, set_trailing_metadata, time_remaining
How can I get a grpc.ServicerContext object? If I can't, how do I test the method?
grpc.ServicerContext is an abstract class defined with the abc module. In your test you need to write your own concrete subclass of it and pass an instance of that to the method you are testing.

Call external function without sending 'self' arg

I'm writing tests for a Django application and using a attribute on my test class to store which view it's supposed to be testing, like this:
# IN TESTS.PY
class OrderTests(TestCase, ShopTest):
_VIEW = views.order
def test_gateway_answer(self):
url = 'whatever url'
request = self.request_factory(url, 'GET')
self._VIEW(request, **{'sku': order.sku})
# IN VIEWS.PY
def order(request, sku)
...
My guess is that the problem I'm having is caused because since I'm calling an attribute of the OrderTests class, python assumes I wanna send self and then order get the wrong arguments. Easy to solve... just not use it as a class attribute, but I was wondering if there's a way to tell python to not send self in this case.
Thanks.
This happens because in Python functions are descriptors, so when they are accessed on class instances they bind their first (assumed self) parameter to the instance.
You could access _VIEW on the class, not on the instance:
class OrderTests(TestCase, ShopTest):
_VIEW = views.order
def test_gateway_answer(self):
url = 'whatever url'
request = self.request_factory(url, 'GET')
OrderTests._VIEW(request, **{'sku': order.sku})
Alternatively, you can wrap it in staticmethod to prevent it being bound to the instance:
class OrderTests(TestCase, ShopTest):
_VIEW = staticmethod(views.order)
def test_gateway_answer(self):
url = 'whatever url'
request = self.request_factory(url, 'GET')
self._VIEW(request, **{'sku': order.sku})

Categories

Resources