How to keep database data when run py.test? - python

I want to test my query db function.
import pytest
from account_system.action import my_action
from account_system.models import MyUser
#pytest.mark.django_db
def test_ok_profile():
req = FakeRequest()
email = 'tester#example.com'
MyUser.objects.create_user(
email=email,
password="example",
)
# query MyUser table and return result
result = my_action.get_profile(email)
assert result == 'success'
But it's fail.
> assert result == 'success'
E assert None == 'success'
The function doesn't get any result from DB.
I check the database data and doesn't see any record.
(ex. User tester#example.com)
How to rewrite my code for testing?
Or how to keep data in database?
Thank you,

This is the way of how databases tests works..
When you set a django_db, the pytest will rollback your data after use.
But if you want to use the same data in several tests, you should take a look on
pytest.fixtures and factory_boy like :
import factory
class UserFactory(factory.DjangoModelFactory):
class Meta:
model = User
#pytest.fixture
def user():
return UserFactory(email = "bla#bla.com", password = "blabla")
And now you apply this reference on your test code:
test_test_ok_profile(user):
assert user.email = "bla#bla.com"

Related

Testdirven.io fastApi course: error in the first stages

I am in the first part of TDD fastapi, docker and pytest course. I got a strange issue, that I need your help with.
When I created the first test that used torotoise it works fine, adds the record to the DB, and gets it from fastapi with no issue.
The minute I add another test (the one that adds, then reads the record), I get this error:
tortoise.exceptions.OperationalError: relation "textsummery" does not exist
(please ignore the typo in the word summary, it started as a mistake, but I use it as a way to force myself to not mindlessly copy paste everything)
If I remove the first test, this error is now gone, and the test goes without a hitch
Any reason why it won't just use the tables already created? I'm kinda at a loss on this one.
Code:
# C:\src\tdd-fastapi\project\test\conftest.py
#pytest.fixture(scope="module")
def test_app_with_db():
# set up
app = create_applications()
app.dependency_overrides[get_settings] = get_settings_override
register_tortoise(
app,
db_url=os.environ.get("DATABASE_TEST_URL"),
modules={"models": ["app.models.tortoise"]},
generate_schemas=True,
add_exception_handlers=True,
)
And
# C:\src\tdd-fastapi\project\test\test_summeries.py
def test_create_summery(test_app_with_db):
response = test_app_with_db.post("/summeries/", data=json.dumps({"url": "https://foo.bar"}))
assert response.status_code == 201
assert response.json()["url"] == "https://foo.bar"
assert response.json()["id"] != 0
# ...
def test_read_summery(test_app_with_db):
response = test_app_with_db.post("/summeries/", data=json.dumps({"url": "https://foo.bar"}))
assert response.status_code == 201
assert response.json()["url"] == "https://foo.bar"
assert response.json()["id"] != 0
summery_id = response.json()["id"]
response = test_app_with_db.get(f"/summeries/{summery_id}/")
assert response.status_code == 200
response_dict = response.json()
assert response_dict["id"] == summery_id
assert response_dict["url"] == "https://foo.bar"
assert response_dict["summery"]
assert response_dict["created_at"]
Per a comment suggestion, I added torotoise and testconf
from tortoise import fields, models
from tortoise.contrib.pydantic import pydantic_model_creator
# C:\src\tdd-fastapi\project\app\models\tortoise.py
class TextSummery(models.Model):
url = fields.TextField()
summery = fields.TextField()
created_at = fields.DatetimeField(auto_now_add=True)
updated_at = fields.DatetimeField(auto_now=True)
def __str__(self):
return self.url
SummerySchema = pydantic_model_creator(TextSummery)
And
# C:\src\tdd-fastapi\project\test\conftest.py
import os
import pytest
from starlette.testclient import TestClient
from app.main import create_applications
from app.config import get_settings, Settings
from tortoise.contrib.fastapi import register_tortoise
def get_settings_override() -> Settings:
return Settings(testing=1, database_url=os.environ.get('DATABASE_TEST_URL'))
#pytest.fixture(scope='module')
def test_app():
# setup
app = create_applications()
app.dependency_overrides[get_settings] = get_settings_override
with TestClient(app) as test_client:
yield test_client
#pytest.fixture(scope="module")
def test_app_with_db():
# set up
app = create_applications()
app.dependency_overrides[get_settings] = get_settings_override
register_tortoise(
app,
db_url=os.environ.get("DATABASE_TEST_URL"),
modules={"models": ["app.models.tortoise"]},
generate_schemas=True,
add_exception_handlers=True,
)
with TestClient(app) as test_client:
# testing
yield test_client
# tear down
Found it!
The problem wasn't in any of the files I included, but in main.py
Inside the create application I had this code:
def create_applications() -> FastAPI:
application = FastAPI()
register_tortoise(
application,
db_url = os.environ.get("DATABASE_URL"),
modules = {"models": ["app.models.tortoise"]},
generate_schemas=False, # updated
add_exception_handlers = True
)
application.include_router(ping.router)
application.include_router(summeries.router, prefix="/summeries", tags=['summeries'])
return application
This was in a previous part of the course, and I forgot to remove it.
I'm not sure why it affected only the 2nd test, but I had a function that should have handled it, called init_db. After removing register_tortoise, all tests work as expected.

how to write pytest for pymssql?

I am trying to write pytest to test the following method by mocking the database. How to mock the database connection without actually connecting to the real database server.I tried with sample test case. I am not sure if that is right way to do it. Please correct me if I am wrong.
//fetch.py
import pymssql
def cur_fetch(query):
with pymssql.connect('host', 'username', 'password') as conn:
with conn.cursor(as_dict=True) as cursor:
cursor.execute(query)
response = cursor.fetchall()
return response
//test_fetch.py
import mock
from unittest.mock import MagicMock, patch
from .fetch import cur_fetch
def test_cur_fetch():
with patch('fetch.pymssql', autospec=True) as mock_pymssql:
mock_cursor = mock.MagicMock()
test_data = [{'password': 'secret', 'id': 1}]
mock_cursor.fetchall.return_value = MagicMock(return_value=test_data)
mock_pymssql.connect.return_value.cursor.return_value.__enter__.return_value = mock_cursor
x = cur_fetch({})
assert x == None
The result is:
AssertionError: assert <MagicMock name='pymssql.connect().__enter__().cursor().__enter__().fetchall()' id='2250972950288'> == None
Please help.
Trying to mock out a module is difficult. Mocking a method call is simple. Rewrite your test like this:
import unittest.mock as mock
import fetch
def test_cur_tech():
with mock.patch('fetch.pymssql.connect') as mock_connect:
mock_conn = mock.MagicMock()
mock_cursor = mock.MagicMock()
mock_connect.return_value.__enter__.return_value = mock_conn
mock_conn.cursor.return_value.__enter__.return_value = mock_cursor
mock_cursor.fetchall.return_value = [{}]
res = fetch.cur_fetch('select * from table')
assert mock_cursor.execute.call_args.args[0] == 'select * from table'
assert res == [{}]
In the above code we're explicitly mocking pymyssql.connect, and
providing appropriate fake context managers to make the code in
cur_fetch happy.
You could simplify things a bit if cur_fetch received the connection
as an argument, rather than calling pymssql.connect itself.

How to mock failed operations with moto, #mock_dynamodb2?

I am currently trying to write unit tests for my python code using Moto & #mock_dynamodb2 . So far it's been working for me to test my "successful operation" test cases. But I'm having trouble getting it to work for my "failure cases".
In my test code I have:
#mock_dynamodb2
class TestClassUnderTestExample(unittest.TestCase):
def setUp(self):
ddb = boto3.resource("dynamodb", "us-east-1")
self.table = ddb.create_table(<the table definition)
self.example_under_test = ClassUnderTestExample(ddb)
def test_some_thing_success(self):
expected_response = {<some value>}
assert expected_response = self.example_under_test.write_entry(<some value>)
def test_some_thing_success(self):
response = self.example_under_test.write_entry(<some value>)
# How to assert exception is thrown by forcing put item to fail?
The TestClassUnderTestExample would look something like this:
class ClassUnderTestExample:
def __init__(self, ddb_resource=None):
if not ddb_resource:
ddb_resource = boto3.resource('dynamodb')
self.table = ddb_resource.Table(.....)
def write_entry(some_value)
ddb_item = <do stuff with some_value to create sanitized item>
response = self.table.put_item(
Item=ddb_item
)
if pydash.get(response, "ResponseMetadata.HTTPStatusCode") != 200:
raise SomeCustomErrorType("Unexpected response from DynamoDB when attempting to PutItem")
return ddb_item
I've been completely stuck when it comes to actually mocking the .put_item operation to return a non-success value so that I can test that the ClassUnderTestExample will handle it as expected and throw the custom error. I've tried things like deleting the table before running the test, but that just throws an exception when getting the table rather than an executed PutItem with an error code.
I've also tried putting a patch for pydash or for the table above the test but I must be doing something wrong. I can't find anything in moto's documentation. Any help would be appreciated!
The goal of Moto is to completely mimick AWS' behaviour, including how to behave when the user supplies erroneous inputs. In other words, a call to put_item() that fails against AWS, would/should also fail against Moto.
There is no build-in way to force an error response on a valid input.
It's difficult to tell from your example how this can be forced, but it looks like it's worth playing around with this line to create an invalid input:
ddb_item = <do stuff with some_value to create sanitized item>
Yes, you can. Use mocking for this. Simple and runnable example:
from unittest import TestCase
from unittest.mock import Mock
from uuid import uuid4
import boto3
from moto import mock_dynamodb2
def create_user_table(table_name: str) -> dict:
return dict(
TableName=table_name,
KeySchema=[
{
'AttributeName': 'id',
'KeyType': 'HASH'
},
],
AttributeDefinitions=[
{
'AttributeName': 'id',
'AttributeType': 'S'
},
],
BillingMode='PAY_PER_REQUEST'
)
class UserRepository:
table_name = 'users'
def __init__(self, ddb_resource):
if not ddb_resource:
ddb_resource = boto3.resource('dynamodb')
self.table = ddb_resource.Table(self.table_name)
def create_user(self, username):
return self.table.put_item(Item={'id': str(uuid4), 'username': username})
#mock_dynamodb2
class TestUserRepository(TestCase):
def setUp(self):
ddb = boto3.resource("dynamodb", "us-east-1")
self.table = ddb.create_table(**create_user_table('users'))
self.test_user_repo = UserRepository(ddb)
def tearDown(self):
self.table.delete()
def test_some_thing_success(self):
user = self.test_user_repo.create_user(username='John')
assert len(self.table.scan()['Items']) == 1
def test_some_thing_failure(self):
self.test_user_repo.table = table = Mock()
table.put_item.side_effect = Exception('Boto3 Exception')
with self.assertRaises(Exception) as exc:
self.test_user_repo.create_user(username='John')
self.assertTrue('Boto3 Exception' in exc.exception)

How do I return an error message in Flask-resful?

I added a check on a Post method to only let appointments on different dates pass through but I don't know how to return an error msg. here's the code
from flask_restful import Resource, Api, request
from package.model import conn
class Appointments(Resource):
def get(self):
appointment = conn.execute("SELECT p.*,d.*,a.* from appointment a LEFT JOIN patient p ON a.pat_id = p.pat_id LEFT JOIN doctor d ON a.doc_id = d.doc_id ORDER BY appointment_date DESC").fetchall()
return appointment
def post(self):
appointment = request.get_json(force=True)
pat_id = appointment['pat_id']
doc_id = appointment['doc_id']
appointment_date = appointment['appointment_date']
a = conn.execute("SELECT count(*) From appointment WHERE doc_id =?
AND appointment_date=?",(doc_id,appointment_date,)).fetchone()
if a['count(*)'] == 0:
appointment['app_id'] = conn.execute('''INSERT INTO appointment(pat_id,doc_id,appointment_date)VALUES(?,?,?)''', (pat_id, doc_id,appointment_date)).lastrowid
conn.commit()
return appointment
else:
pass
what do I return instead of the pass statement?
PS: For context, I'm trying to improve https://github.com/tushariscoolster/HospitalManagementSystem
Flask-Restful provides an abort function, it's can raise an HTTPException with special HTTP code and message back to the client.
So, you can try to change the code like below:
from flask_restful import abort
class Appointments(Resource):
def post(self):
# ignore some code
if a['count(*)'] == 0:
# ignore some code
else:
abort(403, error_message='just accept an appointment on special date')
then, the client will receive 403 and a valid JSON string like below:
{"error_message":"just accept an appointment on special date"}
The last, the client should deal with the error message properly.

DataRequired validator is broken for wtforms.BooleanField

I'm using WTForms (together with Flask, flask-wtf, sqlalchemy) to validate incoming JSON for REST APIs. I realize that WTForms is aimed more towards HTML form rendering and validation, but I chose it because it can autogenerate forms out of my sqlalchemy models (thanks to wtforms.ext.sqlalchemy).
Anyway, here is the problem. One of my models includes boolean field which translates to wtforms.BooleanField with DataRequired validator. The issue is that validation fails with 'This field is required' error message even if I pass correct data. My Form:
class MyForm(Form):
name = TextField('name', validators=[DataRequired()])
disabled = BooleanField('disabled', validators=[DataRequired()])
JSON data is like this:
'{"name": "John", "disabled": "false"}'
What I'm expecting:
{"disabled": "false"} -> validates successful, coerced Python data: {'disabled': False}
{"disabled": "true"} -> validates successful, coerced Python data: {'disabled': True}
{"disabled": ""} or '{"disabled": "foo"}' -> fails validation
Currently in first case validation is failed with {'disabled': [u'This field is required.']}
I know there is a note in docs that says DataRequired validator "require coerced data, not input data", but 1) the form is autogenerated by wtforms.ext.sqlalchemy and 2) how it is supposed to behave if I use InputRequired validator? Check (via form.validate()) that some data exists and then check that this data is "true" or "false"?
To summarize, my question is:
What is the correct way of validating wtforms.BooleanField?
Maybe there is some other framework that can validate incoming JSON against given sqlalchemy models?
Thanks.
There are a number of ways of going about this. You could write your own converter to make of use a radiofield with true/false choices, you could use a data filter, you could set a default value, but I think the behavior you want will be possible with this:
MyForm = model_form(MyModel, db_session=db, field_args = {
'disabled' : {
'false_values': ['false'],
'validators' : [InputRequired()] }
})
EDIT: If you wanted a stricter handler you could do the following:
class BooleanRequired(object):
field_flags = ('required', )
def __init__(self, message=None):
self.message = message
def __call__(self, form, field):
if field.data is None:
if self.message is None:
message = field.gettext('This field is required.')
else:
message = self.message
field.errors[:] = []
raise StopValidation(message)
class StrictBooleanField(BooleanField):
def process_formdata(self, valuelist):
self.data = None
if valuelist:
if valuelist[0] == 'false':
self.data = False
elif valuelist[0] == 'true':
self.data = True
class StrictModelConverter(ModelConverter):
#converts('Boolean')
def conv_Boolean(self, field_args, **extra):
return StrictBooleanField(**field_args)
MyForm = model_form(MyModel, db_session=db, converter=StrictModelConverter(),
field_args = { 'disabled' : { 'validators': [BooleanRequired()] }
})

Categories