How can I properly test 2 parameters function using Pytest? - python

I' trying to properly test this simple function:
def get_content_from_header(request, header_name):
try:
content = request.headers[header_name]
except KeyError:
logging.error(f"BAD REQUEST: '{header_name}' header is missing from the request.")
except AttributeError:
logging.error(f"BAD REQUEST: request has no attributes 'headers'.")
else:
return content
return None
So this is my code so far, I'm using parametrize along with fixture to achieve my goal:
import main
import pytest
class ValidRequest:
def __init__(self):
self.headers = {
'Authorization': 'test_auth'
}
#pytest.fixture
def mocked_request():
request = ValidRequest()
return request
#pytest.mark.parametrize("possible_input, expected_output",
[('Authorization', 'test_auth'),
('InvalidHeader', None)])
def test_get_content_from_header(mocked_request, possible_input, expected_output):
# Run the function with mocked request
assert main.get_content_from_header(mocked_request, possible_input) == expected_output
Here's my problem: I only test the second parameter of the function get_content_from_header, not request which is the first one. How could I properly do that ?
Should I create a new class InvalidRequest and test my function with this new class in a new testing function just below test_get_content_from_header ?
Or should I add this new parameter trough parametrize in the existing testing function ?
What is the cleanest (more pythonic) way to do it ?

I would suggest a little change here, lets simplify that function a bit. Since that we are getting a certain header from the headers dict of the request we can just pass just the headers dict instead of the whole request.
def get_content_from_header(headers: dict, header_name: str):
if header_name in headers.keys():
return headers[header_name]
return None
This works the same way as your function, and you do not have to test your request parameter. Now you can test that in a very simple manner:
def test_get_content_from_header_returning_header_value():
headers = {"Authorization": "test_auth"}
assert get_content_from_header(headers, "Authorization") == "test_auth"
def test_get_content_from_header_returning_none():
headers = {"Authorization": None}
assert get_content_from_header(headers, "Authorization") == None
Now you don't need to test your request in that test, you can refer to https://flask.palletsprojects.com/en/2.0.x/testing/ and more specifically the client usage and test your endpoints, to test your request param.
Now about the loggers, I will usually place those in the place where you actually call the get_content_from_header function.

Related

How to mock client object

I am working on writing unittest for my fastapi project.
One endpoint includes getting a serviceNow ticket. Here is the code i want to test:
from aiosnow.models.table.declared import IncidentModel as Incident
from fastapi import APIRouter
router = APIRouter()
#router.post("/get_ticket")
async def snow_get_ticket(req: DialogflowRequest):
"""Retrieves the status of the ticket in the parameter."""
client = create_snow_client(
SNOW_TEST_CONFIG.servicenow_url, SNOW_TEST_CONFIG.user, SNOW_TEST_CONFIG.pwd
)
params: dict = req.sessionInfo["parameters"]
ticket_num = params["ticket_num"]
try:
async with Incident(client, table_name="incident") as incident:
response = await incident.get_one(Incident.number == ticket_num)
stage_value = response.data["state"].value
desc = response.data["description"]
[...data manipulation, unimportant parts]
What i am having trouble with is trying to mock the client response, every time the actual client gets invoked and it makes the API call which i dont want.
Here is the current version of my unittest:
from fastapi.testclient import TestClient
client = TestClient(app)
#patch("aiosnow.models.table.declared.IncidentModel")
def test_get_ticket_endpoint_valid_ticket_num(self, mock_client):
mock_client.return_value = {"data" : {"state": "new",
"description": "test"}}
response = client.post(
"/snow/get_ticket", json=json.load(self.test_request)
)
assert response.status_code == 200
I think my problem is patching the wrong object, but i am not sure what else to patch.
In your test your calling client.post(...) if you don't want this to go to the Service Now API this client should be mocked.
Edit 1:
Okay so the way your test is setup now the self arg is the mocked IncidentModel object. So only this object will be a mock. Since you are creating a brand new IncidentModel object in your post method it is a real IncidentModel object, hence why its actually calling the api.
In order to mock the IncidentModel.get_one method so that it will return your mock value any time an object calls it you want to do something like this:
def test_get_ticket_endpoint_valid_ticket_num(mock_client):
mock_client.return_value = {"data" : {"state": "new",
"description": "test"}}
with patch.object(aiosnow.models.table.declared.IncidentModel, "get_one", return_value=mock_client):
response = client.post(
"/snow/get_ticket", json=json.load(self.test_request)
)
assert response.status_code == 200
The way variable assignment works in python, changing aiosnow.models.table.declared.IncidentModel will not change the IncidentModel that you've imported into your python file. You have to do the mocking where you use the object.
So instead of #patch("aiosnow.models.table.declared.IncidentModel"), you want to do #patch("your_python_file.IncidentModel")

pytest mock multiple request in one function

I try to write a test case for one of my functions. The function look like the following:
def function(self):
token = self.request_post_get_token()
self.request_post_1(token)
self.request_post_2(token)
return 200
each request_post test is a post request call, and it will return 200 or 401.
how to mock each post request call and return 200 and be able to let me test the whole function?
I'm using pytest and patch.object. Here is something I wrote, but I feel like I did it totally wrong.
def test_function():
Response = nameTuple('Response', 'status_code text')
r = Response(200, "test")
with patch.object(request, 'post', return_value = r):
self.request_post_get_token()
with patch.object(requests, 'post', return_value = r):
self.request_post_1("token")
with patch.object(requests, 'post', return_value = r):
self.request_post_2("token")
You are approaching it the wrong way. When you write a test for a function it should only test the function itself, not all the functions it calls during it's run.
What you want to do is to mock the functions request_post_2 and request_post_1 and to check if they were called with the token using the function assert_called_once
Here is a quick example for you
# Arrange
cls_obj = YourClass()
cls_obj.request_post_2 = Mock()
cls_obj.request_post_1 = Mock()
# Act
cls_obj.function()
# Assert
cls_obj.request_post_2.assert_called_with_once(token)
cls_obj.request_post_1.assert_called_with_once(token)
Assuming each request is in some way unique ie no duplicate requests with different returns, you can register multiple request matchers which will return different results for each request. https://requests-mock.readthedocs.io/en/latest/matching.html

python Mock post inside a method

How can I mock a post inside a method, so i can have unittests?
def send_report(self, data):
url = settings.WEBHOOK_PO
payload = json.dumps(data)
requests.post(url, data=payload)
url = settings.WEBHOOK_LQA
response = requests.post(url, data=payload)
return response.status_code
Is there a way to cover this method for unit test with not actually posting?
You can use the mock library to replace requests.post with something else:
with mock.patch('requests.post') as mock_post:
foo.send_report(data)
(mock is a third-party package, but was added to the standard library, as part of the unittest package`, in Python 3.3.)
mock_post can be configured to provide the desired behavior during the test; consult the mock documentation for details.
Another option is to modify your method to take the post function as an argument, rather than hard-coding the function (this is an example of dependency injection):
def send_report(self, data, poster=requests.post):
url = settings.WEBHOOK_PO
payload = json.dumps(data)
poster(url, data=payload)
url = settings.WEBHOOK_LQA
response = poster(url, data=payload)
return response.status_code
When you want to test the function, you simply pass a different callable object as the optional second argument.
Note that you can supply separate functions for the two types of posts, which might make it easier to test than with a mock:
from functools import partial
def send_report(self,
data,
post_po=partial(requests.post, settings.WEBHOOK_PO),
post_lqa=partial(requests.post, settings.WEBHOOK_LQA)):
payload = json.dumps(data)
post_po(data=payload)
response = post_lqa(data=payload)
return response.status_code

mocking session in requests library

In my python code I have global requests.session instance:
import requests
session = requests.session()
How can I mock it with Mock? Is there any decorator for this kind of operations? I tried following:
session.get = mock.Mock(side_effect=self.side_effects)
but (as expected) this code doesn't return session.get to original state after each test, like #mock.patch decorator do.
Since requests.session() returns an instance of the Session class, it is also possible to use patch.object()
from requests import Session
from unittest.mock import patch
#patch.object(Session, 'get')
def test_foo(mock_get):
mock_get.return_value = 'bar'
Use mock.patch to patch session in your module. Here you go, a complete working example https://gist.github.com/k-bx/5861641
With some inspiration from the previous answer and :
mock-attributes-in-python-mock
I was able to mock a session defined like this:
class MyClient(object):
"""
"""
def __init__(self):
self.session = requests.session()
with that: (the call to get returns a response with a status_code attribute set to 200)
def test_login_session():
with mock.patch('path.to.requests.session') as patched_session:
# instantiate service: Arrange
test_client = MyClient()
type(patched_session().get.return_value).status_code = mock.PropertyMock(return_value=200)
# Act (+assert)
resp = test_client.login_cookie()
# Assert
assert resp is None
I discovered the requests_mock library. It saved me a lot of bother. With pytest...
def test_success(self, requests_mock):
"""They give us a token.
"""
requests_mock.get("https://example.com/api/v1/login",
text=(
'{"result":1001, "errMsg":null,'
f'"token":"TEST_TOKEN",' '"expire":1799}'))
auth_token = the_module_I_am_testing.BearerAuth('test_apikey')
assert auth_token == 'TEST_TOKEN'
The module I am testing has my BearerAuth class which hits an endpoint for a token to start a requests.session with.

Python mock, django and requests

So, I've just started using mock with a Django project. I'm trying to mock out part of a view which makes a request to a remote API to confirm a subscription request was genuine (a form of verification as per the spec I'm working to).
What I have resembles:
class SubscriptionView(View):
def post(self, request, **kwargs):
remote_url = request.POST.get('remote_url')
if remote_url:
response = requests.get(remote_url, params={'verify': 'hello'})
if response.status_code != 200:
return HttpResponse('Verification of request failed')
What I now want to do is to use mock to mock out the requests.get call to change the response, but I can't work out how to do this for the patch decorator. I'd thought you do something like:
#patch(requests.get)
def test_response_verify(self):
# make a call to the view using self.app.post (WebTest),
# requests.get makes a suitable fake response from the mock object
How do I achieve this?
You're almost there. You're just calling it slightly incorrectly.
from mock import call, patch
#patch('my_app.views.requests')
def test_response_verify(self, mock_requests):
# We setup the mock, this may look like magic but it works, return_value is
# a special attribute on a mock, it is what is returned when it is called
# So this is saying we want the return value of requests.get to have an
# status code attribute of 200
mock_requests.get.return_value.status_code = 200
# Here we make the call to the view
response = SubscriptionView().post(request, {'remote_url': 'some_url'})
self.assertEqual(
mock_requests.get.call_args_list,
[call('some_url', params={'verify': 'hello'})]
)
You can also test that the response is the correct type and has the right content.
It's all in the documentation:
patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)
target should be a string in the form ‘package.module.ClassName’.
from mock import patch
# or #patch('requests.get')
#patch.object(requests, 'get')
def test_response_verify(self):
# make a call to the view using self.app.post (WebTest),
# requests.get makes a suitable fake response from the mock object

Categories

Resources