I'd like to test this piece of code:
modify: UserModifyPort = _ports_.user_modify_port
#_app_.route(f"/user", methods=["POST"])
#headers_check({"Accept": "application/json", "Content-Type": "application/json"})
def create_user():
body_json = request.get_json()
body = UserCreateRequest(body_json["username"], body_json["password"])
cmd = UserCreateCmd(body.username, body.password)
# modify usage
user_id = modify.create_user(cmd)
response = UserCreateResponse(user_id)
return response.to_dict(), 201
In this test I need to mock a global variable modify and replace it with object. I've been trying to do this like that:
# TEST
#mock.patch("application.user.user_rest_adapter.modify")
def test_create_user_should_create(modify_mock, db_engine, client, user_config):
modify_mock.return_value = DatabaseUserModifyAdapter(db_engine, user_config)
response = client.post("/user", headers={"Accept": "application/json", "Content-Type": "application/json"},
json={"username": "GALJO", "password": "qwerty123"})
But it isn't executing modify.create_user() function, it just returns some weird object:
<MagicMock name='modify.create_user()' id='140375141136512'>
How can I make this function work?
I solved this issue with sort of workaround. Instead of mocking entire object I've mocked just function that I use. There is no need to use another function, because it is tested in other tests so I replaced it with constant value. I've only checked if given args are correct, everything else is other test task.
#mock.patch("application.user.user_rest_adapter.modify.create_user")
def test_create_user_should_create(create_user_mock, client):
# given
user_id = "a20d7a48-7235-489b-8552-5a081d069078"
create_user_mock.return_value = UUID(user_id)
# when
response = client.post("/user", headers={"Accept": "application/json", "Content-Type": "application/json"},
json={"username": "GALJO", "password": "qwerty123"})
# then
args = create_user_mock.call_args.args
assert args[0].username == "GALJO"
assert args[0].password == "qwerty123"
assert response.json["userID"] == user_id
Related
Hi thank u for your time,
My PyTest always fail the second time on put request.
But when I try testing put using Postman, the issue didn't occur, I manage to put several requests consecutively.
But on PyTest testing, it is successful the first time but always fail the second time. I need to do modifications on JSON request every time for the put test to be successful. am really confuse, I don't know how to debug this.
I am using :
python 3.10
flask 2.1.2
sqlalchemy 1.4.39
#pytest.mark.asyncio
async def test_put_student():
randomFavSub = ""
randomFavSub = randomFavSub.join(random.choice(letters) for i in range(10))
request_dict = {
"user_id": "0e4c1d44-04f6-4a26-a02d-8e67a91b00f1",
"fav_sub": "Subject" + randomFavSub
}
headers = {
'content-type': 'application/json',
'Accepts': 'application/json'
}
async with aiohttp.ClientSession(headers=headers) as session:
await asyncio.sleep(5)
async with session.put(URL + GLOBAL_ID, json=request_dict) as response:
if response.status == 200:
data = await response.json()
data_student = data['student']
data_student_first = data_student[0]
else:
data = await response.text()
assert False, "modify Failure response is text " + str(response.status)
# TODO #38 Generalize the assert to all conditions
for key, value in request_dict.items():
assert (value == data_student_first[key]), "modify FAILURE " + key
The GLOBAL_ID is retrieved from here
#pytest.mark.asyncio
async def test_post_student():
request_dict = {
"user_id": "0e4c1d44-04f6-4a26-a02d-8e67a91b00f1",
"fav_sub": "Science"
}
global GLOBAL_ID
async with aiohttp.ClientSession() as session:
async with session.post(URL, json=request_dict) as response:
if response.status == 200:
data = await response.json()
data_student = data['student']
data_student_first = data_student[0]
GLOBAL_ID = data_student_first['id']
assert GLOBAL_ID, "GLOBAL_ID couldn't be created"
else:
data = await response.text()
assert False, 'retrieve Failure response is text'
for key, value in request_dict.items():
assert value == data_student_first[key], "create FAILURE key"
I run post, get, update, delete request in that order.But only the update seems to fail.
Actually its a mistake from my part, in the put I have two sessions running causing the conflict. If others want details. I can explain further more.
I currently have this locust test:
import logging
from locust import HttpUser, TaskSet, task, constant
log = logging.getLogger("rest-api-performance-test")
def get_headers():
headers = {
"accept": "application/json",
"content-type": "application/json",
}
return headers
def headers_token(auth_token):
headers = {
"accept": "application/json",
"content-type": "application/json",
"auth-token": str(auth_token),
}
return headers
class LoginTasks(TaskSet):
def post_login(self):
headers = get_headers()
login_response = self.client.post("/api/auth",
json={"key": "cstrong#tconsumer.com", "secret": "cstrong"},
headers=headers, catch_response=False, name="Login")
login_data = login_response.json()
auth_token_value = login_data["results"][0]["auth-token"]
return auth_token_value
class UserTasks(UserTasks):
#task
def test_get_list_of_workers(self):
auth_token = self.post_login()
try:
with self.client.get("/api/client/workers",
headers=headers_token(auth_token), catch_response=True,
name="Get Worker Lists") as request_response:
if request_response.status_code == 200:
assert (
'"label": "Salena Mead S"' in request_response.text
)
request_response.success()
log.info("API call resulted in success.")
else:
request_response.failure(request_response.text)
log.error("API call resulted in failed.")
except Exception as e:
log.error(f"Exception occurred! details are {e}")
class WebsiteUser(HttpUser):
host = "https://test.com"
wait_time = constant(1)
tasks = [UserTasks]
the tests runs as expected but post_login is required by multiple tests since is the one who generates the authentication token used by most of the APIs that I'm testing, is there a way to avoid use inheritance from class LoginTasks and find a better solution? The reason I want to avoid it is post_login is not the only method that is going be used many times so I don't want to use multiple inheritance on my UserTasks class.
Any help is appreciated.
Move the function out of the class and pass in the client you want it to use.
def post_login(client):
headers = get_headers()
login_response = client.post("/api/auth",
…
You can then call it when you need it the same way you call get_headers().
auth_token = post_login(self.client)
I have a class called Org, and I'm trying to access its method from multiple functions (that are defined outside of the class). I'm calling main() first, followed by discover_buildings(). The main() executes without error, however, I get AttributeError: 'Org' has no attribute 'headers' error after I call discover_buildings(). What is it that I'm doing wrong? (I was expecting the headers attribute to be shared across the different methods)
class Org(object):
def __init__(self, client_id, client_secret, grant_type='client_credentials'):
self.grant_type = grant_type
self.client_id = client_id
self.client_secret = client_secret
self.url = CI_A_URL
def auth(self):
""" authenticate with bos """
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': self.grant_type
}
r = requests.post(self.url + 'o/token/', data=params)
if r.status_code == 200:
self.access_token = r.json()['access_token']
self.headers = {
'Authorization': 'Bearer %s' %self.access_token,
'Content-Type': 'application/json',
}
else:
logging.error(r.content)
r.raise_for_status()
def get_buildings(self, perPage=1000):
params = {
'perPage': perPage
}
r = requests.get(self.url + 'buildings/', params=params, headers=self.headers)
result = r.json().get('data')
if r.status_code == 200:
buildings_dict = {i['name']: i['id'] for i in result}
sheet_buildings['A1'].value = buildings_dict
else:
logging.error(r.content)
r.raise_for_status()
client_id = 'xxx'
client_secret = 'yyy'
gateway_id = 123
o = Org(client_id, client_secret)
def discover_buildings():
return o.get_buildings()
def main():
return o.auth()
Thanks, in advance, for your help!
Try using a property to calculate headers whenever you need it and then cache it.
def auth(self):
""" authenticate with bos """
# 👇you might want to isolate `token` into a nested #property token
params = {
'client_id': self.client_id,
'client_secret': self.client_secret,
'grant_type': self.grant_type
}
# note assignment to `_headers`, not `headers`
r = requests.post(self.url + 'o/token/', data=params)
if r.status_code == 200:
self._access_token = r.json()['access_token']
# 👆
self._headers = { # 👈
'Authorization': 'Bearer %s' %self._access_token,
'Content-Type': 'application/json',
}
else:
logging.error(r.content)
r.raise_for_status()
#cache after the first time.
_headers = None
#property
def headers(self):
""" call auth when needed
you might want to isolate `token`
into its own property, allowing different
headers to use the same token lookup
"""
if self._headers is None:
self.auth()
return self._headers
the problem is the way you define "discover_buildings"
you define it first with "o" just initialised not after the authentication.
to handle this:
rewrite discover to take 'o' as a parameter
or
check first to see 'o' has 'headers' if not authenticate 'o' and do the rest
def discover_buildings():
if not getattr(o, 'headers'):
o.auth()
return o.get_buildings()
You didn't define self.headers. You need to run o.auth() (or define self.headers) before you run o.get_buildings().
I have an attribute error in a little spotify-api program i am trying to run
my run file contains the following
import os
from spotify_client import AddSongs
def run():
spotify_client = AddSongs('spotify_token')
random_tracks = spotify_client.get_random_tracks()
track_ids = [track['id'] for track in random_tracks]
was_added_to_queue = spotify_client.add_tracks_to_queue()
if was_added_to_queue:
for track in random_tracks:
print(f"Added {track['name']} to your library")
if __name__ == '__main__':
run()
then in spotify_client is the following class
class AddSongs(object):
def __init__(self,):
self.spotify_token = ""
self.uri = ""
def get_random_tracks(self):
wildcard = f'%{random.choice(string.ascii_lowercase)}%'
query = urllib.parse.quote(wildcard)
offset = random.randint(0, 2000)
url = f"https://api.spotify.com/v1/search?q={query}&offset={offset}&type=track&limit=1"
response = requests.get(
url,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {self.spotify_token}"
}
)
response_json = response.json()
print(response)
tracks = [
track for track in response_json['tracks']['items']
]
self.uri = response_json["tracks"]["items"][0]["uri"]
print(f'Found {len(tracks)} tracks to add to your queue')
return tracks
return self.uri
def add_tracks_to_queue(self,):
print('adding to queue...')
url =f"https://api.spotify.com/v1/me/player/queue?uri={self.uri}"
response = requests.post(
url,
headers={
"Content-Type": "application/json",
"Authorization": f"Bearer {self.spotify_token}"
}
)
print(f"Added {track['name']} to your queue")
return response.ok
def callrefresh(self):
print("Refreshing token")
refreshCaller = Refresh()
self.spotify_token = refreshCaller.refresh()
self.get_random_tracks()
a = AddSongs()
a. callrefresh()
As you can see it runs the code fine up untill add_tracks_to_queue
this is giving me the following traceback
Refreshing token
<Response [200]>
Found 1 tracks to add to your queue
Traceback (most recent call last):
File "/Users/shakabediako/Documents/free_streams/run.py", line 18, in <module>
run()
File "/Users/shakabediako/Documents/free_streams/run.py", line 7, in run
spotify_client = AddSongs('spotify_token')
TypeError: __init__() takes 1 positional argument but 2 were given
>>>
I know there are multiple threads about this error but after reading most of them i haven't been able to understand the concept or find the answer.
I think it has something to do with me calling the function from another file but i do not understand why this creates another "positional argument"
I know this because if i just run the spotify_client file i get the following response
Refreshing token
<Response [200]>
Found 1 tracks to add to your queue
>>>
which are just my print values up until def add_tracks_to_queue (which i also don't understand why it is not running)
I really hope someone can help me with this
Thanks in advance
Your AddSongs class doesn't accept any variables as input (also there's a stray comma in the __init__ which doesn't seem to serve any purpose):
class AddSongs(object):
def __init__(self,):
Yet you're calling it as if it does accept a variable:
spotify_client = AddSongs('spotify_token')
The self parameter has to be in there, but if you want to accept any other variables, you need to actually define them. The solution could therefore be to add a token parameter:
class AddSongs(object):
def __init__(self, token=""): # defaults to an empty string if token is not passed in
self.spotify_token = token
That way, if you call AddSongs('spotify_token'), the class's self.spotify_token will get set to 'spotify_token'.
I have 2 functions which taking header of same. Can i save at global so that i no need to call every time
def funct1():
json_d = {"group_id": "uid"}
headers = {"account-id":"xxx","api-key":"xxx","Content-Type": "application/json"}
response = requests.post("https://example.com/docs",headers=headers,json=json_d)
def funct2():
json_d = {"group_id": "uid"}
headers = {"account-id":"xxx","api-key":"xxx","Content-Type": "application/json"}
response = requests.post("https://example.com/docs",headers=headers,json=json_d)
Can I do
headers = {"account-id":"xxx","api-key":"xxx","Content-Type": "application/json"}
global headers
i'd recommend making a function for a default http call.
In this case only the json_d is different per function. Maybe this will also be the URL or other things, you can easy move them up to the initial functions and parameterize them in the default_post() function.
Using **kwargs you can make it more generic, for example if you want to pass a timeout you can call default_post(..., timeout=3) which is passed automatically to the requests.post function.
Example
def funct1():
json_d_1 = {
"group_id": "uid_1"
}
default_post(json_d_1)
def funct2():
json_d_2 = {
"group_id": "uid_2"
}
default_post(json_d_2)
def default_post(json_d, ..., **kwargs):
headers = {"account-id":"xxx","api-key":"xxx","Content-Type": "application/json"}
response = requests.post(
"https://example.com/docs",
headers=headers,
json=json_d,
**kwargs
)
return response
I would suggest to create a function that will return the headers dictionary in order to "protect" it from changes:
def get_headers():
return {"account-id":"xxx","api-key":"xxx","Content-Type": "application/json"}
In this case at every call to the get_headers function new dictionary will be created so if you change it (the headers dictionary) in one function it does not effect the other function (unless what is that you are trying to achieve).