I have an async function that calls a streaming api. What is the best way to write unit test for this function? The api response has to be mocked.
I tried with aiounittest and used mock from unittest. But this calls the actual api instead of getting the mocked response. Also tried with pytest.mark.asyncio annotation, but this kept giving me the error - coroutine was never awaited. I have verified that pytest-asyncio has been installed.
I am using VS Code and Python 3.6.6
Here is the relevant code snippet:
async def method1():
response = requests.get(url=url, params=params, stream=True)
for data in response.iter_lines():
# processing logic here
yield data
Pasting some of the tests I tried.
def mocked_get(*args, **kwargs):
#implementation of mock
class TestClass (unittest.TestCase):
#patch("requests.get", side_effect=mocked_get)
async def test_method (self, mock_requests):
resp = []
async for data in method1:
resp.append (data)
#Also tried await method1
assert resp
Also tried with class TestClass (aiounittest.AsyncTestCase):
Use asynctest instead of aiounittest.
Replace unittest.TestCase with asynctest.TestCase.
Replace from unittest.mock import patch with from asynctest.mock import patch.
async for data in method1: should be async for data in method1():.
import asynctest
from asynctest.mock import patch
class TestClass(asynctest.TestCase):
#patch("requests.get", side_effect=mocked_get)
async def test_method(self, mock_requests):
resp = []
async for data in method1():
resp.append(data)
assert resp
Related
I am using https://pypi.org/project/fastapi-microsoft-identity/ package for AD Azure authorization using requires_auth decorator. From my tests would like to avoid the authorization process but I cannot disable this decorator always returns 401.
this is the following code I am using from my test
def mock_decorator(*args, **kwargs) :
def decorator(f)
wraps(f)
def requires_auth(*args, **kwargs):
return f(*args, **kwargs)
return requires_auth
patch('fastapi_microsoft_identity.requires_auth', mock_decorator).start()
#pytest.fixture
def client():
with TestClient(api) as client:
yield client
def test_api_endpoint(client):
response = client.get("/api/weather/london")
assert response.status_code == 200
Since I am patching auth decorator should return 200 status code, instead, I am getting 401 UnAuthorized
The following code from api
#router.get('/api/weather/{city}')
#requires_auth
async def weather(request: Request, loc: Location = Depends(), units: Optional[str] = 'metric'):
try:
validate_scope(expected_scope, request)
return await openweather_service.get_report_async(loc.city, loc.state, loc.country, units)
except AuthError as ae:
return fastapi.Response(content=ae.error_msg, status_code=ae.status_code)
except ValidationError as ve:
return fastapi.Response(content=ve.error_msg, status_code=ve.status_code)
except Exception as x:
return fastapi.Response(content=str(x), status_code=500)
please help what I am doing wrong.
You can't really bypass a decorator but can circumnavigate it if in its implementation it uses #wrap() in the code.
Now # require_auth does use a #wrap in its implementation so we can circumnavigate the decorator by calling the original function in this case ‘weather’ function as:-
weather.__wrapped__()
instead of the usual way of calling the function.
The __wrapped__ method basically has reference to the original function instead of the decorator.
Here I created a small API which basically returns two strings now here I have called the weather function without the .__wrapped__
# main.py
from fastapi_microsoft_identity import requires_auth, validate_scope, AuthError
import fastapi
from fastapi import FastAPI
app = FastAPI()
#requires_auth
async def weather() :
return False
#app.get("/")
async def root():
if await weather.() :
return {"Decorator is working and not allowing the function f() to work "}
else :
return {"Decorator is disabled"}
Here the weather () doesn’t get executed that is why the string “Decorator is working and not allowing the function f() to work” is returned but in your case it is giving unauthorize error
But now when I call it using wrapped
# main.py
from fastapi_microsoft_identity import requires_auth, validate_scope, AuthError
import fastapi
from fastapi import FastAPI
app = FastAPI()
#requires_auth
async def weather() :
return False
#app.get("/")
async def root():
if await weather.__wrapped__() :
return {"Decorator is working and not allowing the function f() to work "}
else :
return {"Decorator is disabled"}
Now that I have used the,__wrapped__ while calling the weather function it is being executed directly circumnavigating the decorator
So if you don’t want the require#authto work call the weather function like this weather.__wrapped__(<arguments>)
Refer this python docs on wrapper method and this gihub repo on require_auth.
I am trying to test my functions on my django api that perform external requests to external api. How can
i test the following scenarios: success, failed, and exceptions like timeout
The following is a simplified functionality
def get_quote(*args):
# log request
try:
response = requests.post(url, json=data)
# parse this response
except:
# log file :)
finally:
# log_response(...)
return parsed_response or None
None: response can be success, failed, can timeout. I want to test those kind of scenarios
You can mock the result of calling the external API and set an expected return value in the test function:
from unittest.mock import patch
from django.test import TestCase
class ExternalAPITests(TestCase):
#patch("requests.post")
def test_get_quote(self, mock):
mock.return_value = "predetermined external result"
self.assertEquals("expected return value", get_quote())
You can use the responses package - https://pypi.org/project/responses/
import unittest
import responses
from your_package import get_quote
class TestPackage(unittest.TestCase):
#responses.activate
def test_get_quote(self):
url = "http://some_fake_url.com"
responses.add(responses.POST, url, json={"test": "ok"}, status=200)
self.assertDictEqual({"test": "ok"}, get_quote(url))
#responses.activate
def test_get_quote_with_exception(self):
url = "http://some_fake_url.com"
responses.add(responses.POST, url, body=Exception('...'))
with self.assertRaises(Exception):
get_quote(url)
I have a simple asynchronous functions that fetches JSON from the web:
async def fetch (session : aiohttp.ClientSession, url : str, pageSize : int = 100, pageNumber : int = 0):
async with session.get(url, params={'pageSize' : f"{pageSize}", 'pageNumber' : f"{pageNumber}"}) as response:
return await response.json()
How do I write unit tests for it? I am learning how to do that in Python currently and I have settled down on using unittest and aiounittest modules. The scaffolding that I use for testing is as follows:
class AsyncTest(aiounittest.AsyncTestCase):
async def test_fetch(self):
mocked_session = ... #no idea how to create
json = await fetch(mocked_session, "url_for_mock", 10, 0)
I think I would have to somehow mock the session argument using unittest.Mock() functionality to return some harcoded json data but I am a little lost on how to do this so it works inside async with statement. I have never used an unit testing library in Python and an example would be very helpful here. I am using Python 3.8 and aiohttp for networking.
I was able to mock the asynchronous resource manager by implementing my own version of __aenter__ magic method and using AsyncMock to mock relevant async methods. The proof of concept test that mocks network calls looks like follows:
class AsyncTest(aiounittest.AsyncTestCase):
async def test_fetch(self):
request_mock = AsyncMock()
request_mock.__aenter__.return_value = request_mock
request_mock.json.return_value = { 'hello' : 'world'}
session = Mock()
session.get.return_value = request_mock
json = await fetch(session, "url_for_mock", 10, 0)
I am using pytest, I have a test like:
def test_fun1(client):
rsp = client.get(url)
Inside this get url which in my flask server, their is a function fun2(),
# restful api
def get():
res1 = fun2()
# do thing on res1
return res2
I want mock this fun2(), in my pytest test_fun1, with patch.object() and Mock()not work, any suggestion? Thanks!
I have a function that needs to utilize a fixture in my test suite. This is just a small helper function that helps to generate a full URL.
def gen_url(endpoint):
return "{}/{}".format(api_url, endpoint)
I have a fixture in conftest.py that returns the URL:
#pytest.fixture(params=["http://www.example.com"])
def api_url(request):
return request.param
#pytest.fixture(params=["MySecretKey"])
def api_key(request):
return request.param
Finally, in my test function, I need to call my gen_url:
def test_call_action_url(key_key):
url = gen_url("player")
# url should equal: 'http://www.example.com/player'
# Do rest of test here...
When I do this, though, it throws an error saying that api_url isn't defined when gen_url is called. If I add api_url as a second parameter, I need to pass it as a second parameter. That's...not what I want to do.
Can I add api_url as a second parameter to gen_url without being required to pass it from the tests? Why can't I use it like api_key in my test_* function?
If you make gen_url a fixture, it can request api_url without explicitly passing it:
#pytest.fixture
def gen_url(api_url):
def _gen_url(endpoint):
return '{}/{}'.format(api_url, endpoint)
return _gen_url
def test_call_action_url(api_key, gen_url):
url = gen_url('player')
# ...
Additionally, if api_key is only used to make requests, a TestClient class
could encapsulate it, so test methods would only require the client:
try:
from urllib.parse import urljoin # Python 3
except ImportError:
from urlparse import urljoin # Python 2
import requests
#pytest.fixture
def client(api_url, api_key):
class TestClient(requests.Session):
def request(self, method, url, *args, **kwargs):
url = urljoin(api_url, api_key)
return super(TestClient, self).request(method, url, *args, **kwargs)
# Presuming API key is passed as Authorization header
return TestClient(headers={'Authorization': api_key})
def test_call_action_url(client):
response = client.get('player') # requests <api_url>/player
# ...
multiple problems with your code, the fixture is not visible in your test code untill and unless you use it as your test parameter, you are not passing both the fixtures (api_url and api_key) to your test function and subsequently to your helper function.
Here is the modified code (untested)
def gen_url(api_url, endpoint):
return "{}/{}".format(api_url, endpoint)
def test_call_action_url(api_url, api_key):
url = gen_url(api_url, "player")
# url should equal: 'http://www.example.com/player'
# Do rest of test here with api_key here...