I'm following a talk on Flask about creating an API. I want to write some tests for it. Upon testing creating a resource does testing deleting the resource in another function work? How do I make the creation of a resource persist to be tested for deletion and editing?
David Baumgold - Prototyping New APIs with Flask - PyCon 2016
The talk shows how to make an API for the names and image urls of puppies.
you create a puppy at the index page with a POST request
you get a single puppy with a GET from '/puppy_name'
you get the list of puppies with a GET from '/'
you edit a puppy with a PUT from '/puppy_name' (along with the new data of course)
you delete a puppy with a DELETE from '/puppy_name'
import py.test
import unittest
from requests import get, post, delete, put
localhost = 'http://localhost:5000'
class TestApi(unittest.TestCase):
def test_list_puppies(self):
index = get(localhost)
assert index.status_code == 200
def test_get_puppy(self):
puppy1 = get(localhost + '/rover')
puppy2 = get(localhost + '/spot')
assert puppy1.status_code == 200 and puppy2.status_code == 200
def test_create_puppy(self):
create = post(localhost, data={
'name': 'lassie', 'image_url': 'lassie_url'})
assert create.status_code == 201
#py.test.mark.skip('cannot fix it')
def test_edit_puppy(self):
ret = put(localhost + '/lassie',
data={'name': 'xxx', 'image_url': 'yyy'})
assert ret.status_code == 201
def test_puppy_exits(self):
lassie = get(localhost + '/lassie').status_code
assert lassie == 200
def test_delete_puppy(self):
ret = delete(localhost + '/lassie')
assert ret.status_code == 200
#app.route('/', methods=['POST'])
def create_puppy():
puppy, errors = puppy_schema.load(request.form)
if errors:
response = jsonify(errors)
response.status_code = 400
return response
puppy.slug = slugify(puppy.name)
# create in database
db.session.add(puppy)
db.session.commit()
# return an HTTP response
response = jsonify( {'message': 'puppy created'} )
response.status_code = 201
location = url_for('get_puppy', slug=puppy.slug)
response.headers['location'] = location
return response
#app.route('/<slug>', methods=['DELETE'])
def delete_puppy(slug):
puppy = Puppy.query.filter(Puppy.slug == slug).first_or_404()
db.session.delete(puppy)
db.session.commit()
return jsonify( {'message': '{} deleted'.format(puppy.name)} )
The assert statements in both 'test_edit_puppy' and 'test_puppy_exists' fails. I get a 404 status code instead of 201 and 200.
You're testing the wrong thing. You can persist changes to a database, simply by committing it, when you run a test, but you really don't want to do that.
With unit testing you're testing simple units. With integration testing, which is what you're talking about here, you still want each test to have a specific focus. In this particular case you would want to do something like:
def test_delete_puppy(self):
create = post(localhost, data={
'name': 'lassie', 'image_url': 'lassie_url'})
lassie = get(localhost + '/lassie')
# not sure if there's an "assume" method, but I would
# use that here - the test is not a valid test
# if you can't create a puppy and retrieve the puppy
# then there's really no way to delete something that
# does not exist
assert create.status_code == 201
assert lassie.status_code == 200
ret = delete(localhost + '/lassie')
assert ret.status_code == 200
lassie = get(localhost + '/lassie')
assert lassie.status_code == 404
The focus of this test is to test that deleting a puppy works fine. But you want to setup the puppy in the database as either part of your test or part of the setup of your test. It's the arrange portion of arrange, act, assert. You're arranging the state of the world in its proper order before you actually perform the test. The difference between integration testing and unit testing is that with unit testing you'd be mocking out all the endpoints that you're calling, while with integration tests you're going to actually setup all the data that you need before you run the test portion. Which is what you want to do here.
Related
I have a function that I am trying to test in querySomething.py:
class QuerySomething:
def retrieveIssues(self,token):
responses = []
if "customFields" in self._event:
if not self.custom_fields:
fields = []
else:
fields = self.custom_fields
else:
fields = []
for issueTypeKey, issueTypeValue in self.issueTypes.items():
print(issueTypeKey, ":", issueTypeValue)
query = self.getQuery(issueTypeValue, self.status, fields)
respons = httpClient.get_request(query, token)
responses.append(respons)
return responses
And the test file:
def mock_getQuery():
return "QUERY"
def mock_response(state):
if state=="unauth":
with open("src/tests/mockdata/unauthorized_api_response.json","r") as response_file:
unauth_error = response_file.read()
return json.dumps(unauth_error)
elif state=="auth":
with open("src/tests/mockdata/success_api_response.json","r") as response_file:
success_message = response_file.read()
return json.dumps(success_message)
return "No message"
class test_query(unittest.TestCase):
#mock.patch("querySomething.QuerySomething.getQuery", side_effect=mock_getQuery)
#mock.patch("httpClient.get_request", side_effect=mock_response)
def test_retreiveIssues_unauth_response(self,mock_get,QuerySomething):
self.assertEqual(QuerySomething.retrieveIssues("token"),mock_response("unauth"))
if __name__ == "__main__":
unittest.main()
I am trying to mock the httpClient.get_request so that it gets the JSON file instead of reaching out to the API. We want to test an unauthorized response and a success response which explains the mock_response function. However, when I run the test, I get the following:
AssertionError: <MagicMock name='getQuery.retri[36 chars]712'> != '"{\\n \\"errorMessages\\": [\\n [131 chars]\n}"'
which is somewhat correct, but we need just the text, not the object. I read that I need to call the function, but when I try to call the function it throws a ModuleNotFound or NotAPackage error. What do I need to do to mock the httpClient.get_request and return the JSON string in the retrieveIssues function?
Updated, I was able to pull the JSON from the other file, and then was able to mock the return value as follows:
QuerySomething.retrieveIssues.return_value=load_json("unauth")
where load_json("unauth") pulls from the JSON response file.
I am inserting the data in DB and I make an API call to the endpoint I am testing with row id. I have a parameterized test which runs the fixtures multiple times.
#pytest.mark.parametrize(
"endpoint",
[
"/github/access-form",
"/github/issue-form",
],
)
def test_marketplace_details(
client: TestClient, session: Session, endpoint: str, add_marketplace_product_materio_ts: MarketplaceProductLink
):
# here I want to know the id of inserted record. I guess I can get it from the count of fixture "add_marketplace_product_materio_ts" run
r = client.get(f"{endpoint}?marketplace=1")
assert r.status_code == 200
data = r.json()
assert data["marketplaces"] == IsList(
IsPartialDict(
name="themeselection",
purchase_verification_url="https://google.com",
)
)
assert data["brands"] == []
assert data["product_w_technology_name"] == []
Hence, How can I get the count of fixture run in test so I can pass the correct id to r = client.get(f"{endpoint}?marketplace=1"). marketplace=1 here 1 should be count of fixture run.
Thanks.
You can use enumerate:
#pytest.mark.parametrize("idx, endpoint", enumerate(["zero", "one"]))
def test_marketplace_details(idx, endpoint):
print(idx, endpoint)
# prints:
# 0 zero
# 1 one
I'm using a 3rd party library "mailjet" to send email.
Here's the doc: https://dev.mailjet.com/email/guides/getting-started/#prerequisites
This is the method in send_email.py that I want to test:
from mailjet_rest import Client
def send_email(email_content):
client = Client(auth=(api_key, api_secret))
response = client.send.create(data=email_content)
if response.status_code != 200:
// raise the error
else:
print(response.status_code)
return response.status_code
...
I wanted to mock the status code. This is what I tried in test_send_email.py:
from unittest.mock import MagicMock, patch
import unittest
import send_email
class TestClass(unittest.TestCase):
#patch("send_email.Client")
def test_send_email(self, _mock):
email_content = {...}
_mock.return_value.status_code = 200
response = send_email(email_content)
// assert response == 200
When I run the test, it didn't print out the status code, it prints:
<MagicMock name='Client().send.create().status_code' id='140640470579328'>
How can I mock the status_code in the test? Thanks in advance!!!!!
You are putting the attribute in the wrong place.
You mock the Client, which is correct, and put something in its return_value, which is also good.
However, the line
response = client.send.create(data=email_content)
applies on the return_value of your mock (client), the send attribute and its create method.
So what you need is to set the return_value.status_code of this create method.
class TestClass(unittest.TestCase):
#patch("send_email.Client")
def test_send_email(self, mock_client):
email_content = {...}
# this puts 200 in Client().status_code
# mock_client.return_value.status_code = 200
# that puts 200 in Client().send.create().status_code
mock_client.return_value.send.create.return_value.status_code = 20
response = send_email(email_content)
assert response == 200
I'm testing Trello API, creating and deleting cards. The only assertions I have here are for status code. How can I add some more 'advanced' assertions to the below code?
import unittest
from post_and_delete import *
class TestBasic(unittest.TestCase):
def test_post_delete(self):
# create new card with random name
name = nonce(10)
result_post = post(name)
self.assertEqual(result_post.json()['name'], name)
self.assertEqual(result_post.status_code, 200)
card_id = result_post.json()['id']
# get the card, verify it exists, status code should be 200
result_get = get(card_id)
self.assertEqual(result_get.status_code, 200)
# delete the card, check again if status code is 200
result = delete(card_id)
self.assertEqual(result.status_code, 200)
# get the recently deleted card, status code should be 404
result_get = get(card_id)
self.assertEqual(result_get.status_code, 404)
# try to delete the card again, status code should be 404
result = delete(card_id)
self.assertEqual(result.status_code, 404)
if __name__ == '__main__':
unittest.main()
In my unit test I have the following setUp function:
#patch("FrontEnd.FrontEnd.get_cname")
def setUp(self, mock_get_cname):
mock_requests = MagicMock()
mock_requests.return_value.json.side_effect = return_side_effect
mock_requests.return_value.status_code = 200
mock_requests.return_value.text = 'ssl'
self.requests = patch.object(requests, 'get', mock_requests)
self.requests.start()
In one test I have the following:
#patch("requests.get")
def test_search_resource(self, mock_get):
"""Test for _search_resource"""
mock_get.return_value.status_code = 200
self.fe._search_resource("hulahoop")
self.assertTrue(mock_get.call_args,
call('www.example.com/cdn_resources.json?q=hulahoop',
auth=('user', 'password'),
timeout=5))
mock_get.reset_mock()
mock_get.return_value.status_code = 400
with self.assertRaises(Exception):
self.fe._search_resource("hulahoop")
I am repeating the mock_get here although it has been patched in setUp, for the sole reason of being able to assign it different status_code. How do I assign different values to my status_code without patching requests.get again ?