How to mock Django queryset which was got from related_name? - python

I want to unittest this function, which is service-function inside my Django app:
from datetime import date, timedelta
from account.models import Profile
from lesson.models import Lesson, Question
from repeat.models import RepetitionSession
class QuestionService:
#staticmethod
def get_next_question_by_rep_session(rep_session: RepetitionSession) -> Question:
today_date = date.today()
questions_filter = rep_session.questions.filter(next_repeat_at=today_date)
sorted_questions_filter = questions_filter.order_by('edited_at')
next_question = sorted_questions_filter.first()
return next_question
It was like this, but I've separated it for 3 lines:
next_question = rep_session.questions.filter(next_repeat_at=today_date).order_by('edited_at').first()
Here is my unittest:
from datetime import date, timedelta
from random import randint
from unittest.mock import patch, MagicMock
from django.test import SimpleTestCase
from lesson.services import QuestionService
class QuestionServicesTest(SimpleTestCase):
FILE_PATH = 'lesson.services.question_service'
def test_get_next_question_by_rep_session(self):
test_query = MagicMock()
test_question = MagicMock()
test_rep_session = MagicMock()
test_rep_session.questions = test_query
test_query.filter().return_value = test_query
test_query.order_by().return_value = test_query
test_query.first().return_value = test_question
result = QuestionService.get_next_question_by_rep_session(test_rep_session)
test_query.filter().assert_called_once_with(next_repeat_at=date.today())
I'm getting this fail
https://i.stack.imgur.com/85Dzl.png
AssertionError: Expected 'mock' to be called once. Called 0 times.
Calls: [call.order_by('edited_at'), call.order_by().first()].
So if I want to test filter() I'm usually do this:
#patch(f'{FILE_PATH}.Model_name.objects.filter')
def test_example_func(self, patch_filter):
test_attr = MagickMock()
test_query = MagickMock()
patch_filter.return_value = test_query
result = example_func(test_attr)
patch_filter.assert_called_once_with(test_attr)
Please, help me find what I've missed.

I figured out my mistake. Lets see changes:
def test_get_next_question_by_rep_session(self):
test_query = MagicMock()
expected_question = MagicMock()
fail_question = MagicMock()
test_rep_session = MagicMock()
test_rep_session.questions = test_query
test_query.filter.return_value = test_query # Mistake was here
test_query.order_by.return_value = test_query # Mistake was here
test_query.first.return_value = expected_question # Mistake was here
result = QuestionService.get_next_question_by_rep_session(test_rep_session)
test_query.filter.assert_called_once_with(next_repeat_at=date.today()) # Mistake was here
test_query.order_by.assert_called_once_with('edited_at')
test_query.first.assert_called_once()
self.assertEqual(result, expected_question)
self.assertNotEqual(result, fail_question)
So when we mock objects it works like that:
x.return_value = 420
# When we will call x we will get 420
>>> x()
>>> 420
def x() -> y:
...
x().return_value = 665
# However when we will call result of the x() we will get 665
>>> y
>>> 665
If I did mistake somewhere please let me know.

Related

Mocking sqlobject function call for test db

I am trying to mock sqlbuilder.func for test cases with pytest
I successfully mocked sqlbuilder.func.TO_BASE64 with correct output but when I tried mocking sqlbuilder.func.FROM_UNIXTIME I didn't get any error but the resulted output is incorrect with the generated query. Below is the minimal working example of the problem.
models.py
from sqlobject import (
sqlbuilder,
sqlhub,
SQLObject,
StringCol,
BLOBCol,
TimestampCol,
)
class Store(SQLObject):
name = StringCol()
sample = BLOBCol()
createdAt = TimestampCol()
DATE_FORMAT = "%Y-%m-%d"
def retrieve(name):
query = sqlbuilder.Select([
sqlbuilder.func.TO_BASE64(Store.q.sample),
],
sqlbuilder.AND(
Store.q.name == name,
sqlbuilder.func.FROM_UNIXTIME(Store.q.createdAt, DATE_FORMAT) >= sqlbuilder.func.FROM_UNIXTIME("2018-10-12", DATE_FORMAT)
)
)
connection = sqlhub.getConnection()
query = connection.sqlrepr(query)
print(query)
queryResult = connection.queryAll(query)
return queryResult
conftest.py
import pytest
from models import Store
from sqlobject import sqlhub
from sqlobject.sqlite import sqliteconnection
#pytest.fixture(autouse=True, scope="session")
def sqlite_db_session(tmpdir_factory):
file = tmpdir_factory.mktemp("db").join("sqlite.db")
conn = sqliteconnection.SQLiteConnection(str(file))
sqlhub.processConnection = conn
init_tables()
yield conn
conn.close()
def init_tables():
Store.createTable(ifNotExists=True)
test_ex1.py
import pytest
from sqlobject import sqlbuilder
from models import retrieve
try:
import mock
from mock import MagicMock
except ImportError:
from unittest import mock
from unittest.mock import MagicMock
def TO_BASE64(x):
return x
def FROM_UNIXTIME(x, y):
return 'strftime("%Y%m%d", datetime({},"unixepoch", "localtime"))'.format(x)
# #mock.patch("sqlobject.sqlbuilder.func.TO_BASE64")
# #mock.patch("sqlobject.sqlbuilder.func.TO_BASE64", MagicMock(side_effect=lambda x: x))
# #mock.patch("sqlobject.sqlbuilder.func.TO_BASE64", new_callable=MagicMock(side_effect=lambda x: x))
#mock.patch("sqlobject.sqlbuilder.func.TO_BASE64", TO_BASE64)
#mock.patch("sqlobject.sqlbuilder.func.FROM_UNIXTIME", FROM_UNIXTIME)
def test_retrieve():
result = retrieve('Some')
assert result == []
Current SQL:
SELECT store.sample FROM store WHERE (((store.name) = ('Some')) AND (1))
Expected SQL:
SELECT
store.sample
FROM
store
WHERE
store.name = 'Some'
AND
strftime(
'%Y%m%d',
datetime(store.created_at, 'unixepoch', 'localtime')
) >= strftime(
'%Y%m%d',
datetime('2018-10-12', 'unixepoch', 'localtime')
)
Edit Example
#! /usr/bin/env python
from sqlobject import *
__connection__ = "sqlite:/:memory:?debug=1&debugOutput=1"
try:
import mock
from mock import MagicMock
except ImportError:
from unittest import mock
from unittest.mock import MagicMock
class Store(SQLObject):
name = StringCol()
sample = BLOBCol()
createdAt = TimestampCol()
Store.createTable()
DATE_FORMAT = "%Y-%m-%d"
def retrieve(name):
query = sqlbuilder.Select([
sqlbuilder.func.TO_BASE64(Store.q.sample),
],
sqlbuilder.AND(
Store.q.name == name,
sqlbuilder.func.FROM_UNIXTIME(Store.q.createdAt, DATE_FORMAT) >= sqlbuilder.func.FROM_UNIXTIME("2018-10-12", DATE_FORMAT)
)
)
connection = Store._connection
query = connection.sqlrepr(query)
queryResult = connection.queryAll(query)
return queryResult
def TO_BASE64(x):
return x
def FROM_UNIXTIME(x, y):
return 'strftime("%Y%m%d", datetime({},"unixepoch", "localtime"))'.format(x)
for p in [
mock.patch("sqlobject.sqlbuilder.func.TO_BASE64",TO_BASE64),
mock.patch("sqlobject.sqlbuilder.func.FROM_UNIXTIME",FROM_UNIXTIME),
]:
p.start()
retrieve('Some')
mock.patch.stopall()
By default, sqlbuilder.func is an SQLExpression that passes its attribute (sqlbuilder.func.datetime, e.g.) to the SQL backend as a constant (sqlbuilder.func actually is an alias for sqlbuilder.ConstantSpace). See the docs about SQLExpression, the FAQ and the code for func.
When you mock an attribute in func namespace it's evaluated by SQLObject and passed to the backend in reduced form. If you want to return a string literal from the mocking function you need to tell SQLObject it's a value that has to be passed to the backend as is, unevaluated. The way to do it is to wrap the literal in SQLConstant like this:
def FROM_UNIXTIME(x, y):
return sqlbuilder.SQLConstant('strftime("%Y%m%d", datetime({},"unixepoch", "localtime"))'.format(x))
See SQLConstant.
The entire test script now looks this
#! /usr/bin/env python3.7
from sqlobject import *
__connection__ = "sqlite:/:memory:?debug=1&debugOutput=1"
try:
import mock
from mock import MagicMock
except ImportError:
from unittest import mock
from unittest.mock import MagicMock
class Store(SQLObject):
name = StringCol()
sample = BLOBCol()
createdAt = TimestampCol()
Store.createTable()
DATE_FORMAT = "%Y-%m-%d"
def retrieve(name):
query = sqlbuilder.Select([
sqlbuilder.func.TO_BASE64(Store.q.sample),
],
sqlbuilder.AND(
Store.q.name == name,
sqlbuilder.func.FROM_UNIXTIME(Store.q.createdAt, DATE_FORMAT) >= sqlbuilder.func.FROM_UNIXTIME("2018-10-12", DATE_FORMAT)
)
)
connection = Store._connection
query = connection.sqlrepr(query)
queryResult = connection.queryAll(query)
return queryResult
def TO_BASE64(x):
return x
def FROM_UNIXTIME(x, y):
return sqlbuilder.SQLConstant('strftime("%Y%m%d", datetime({},"unixepoch", "localtime"))'.format(x))
for p in [
mock.patch("sqlobject.sqlbuilder.func.TO_BASE64",TO_BASE64),
mock.patch("sqlobject.sqlbuilder.func.FROM_UNIXTIME",FROM_UNIXTIME),
]:
p.start()
retrieve('Some')
mock.patch.stopall()
The output is:
1/Query : CREATE TABLE store (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
sample TEXT,
created_at TIMESTAMP
)
1/QueryR : CREATE TABLE store (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
sample TEXT,
created_at TIMESTAMP
)
2/QueryAll: SELECT store.sample FROM store WHERE (((store.name) = ('Some')) AND ((strftime("%Y%m%d", datetime(store.created_at,"unixepoch", "localtime"))) >= (strftime("%Y%m%d", datetime(2018-10-12,"unixepoch", "localtime")))))
2/QueryR : SELECT store.sample FROM store WHERE (((store.name) = ('Some')) AND ((strftime("%Y%m%d", datetime(store.created_at,"unixepoch", "localtime"))) >= (strftime("%Y%m%d", datetime(2018-10-12,"unixepoch", "localtime")))))
2/QueryAll-> []
PS. Full disclosure: I'm the current maintainer of SQLObject.
As #phd pointed that SQLObject evaluate the expression before passing it to backend in reducted form.
Then we can also pass expression directly which SQLObject will evaluate so instead of passing string literal we can also do as below
def FROM_UNIXTIME(x, y):
return sqlbuilder.func.strftime("%Y%m%d", sqlbuilder.func.datetime(x, "unixepoch", "localtime"))
Output:
SELECT store.sample FROM store WHERE (((store.name) = ('Some')) AND ((strftime("%Y%m%d", datetime(store.created_at,"unixepoch", "localtime"))) >= (strftime("%Y%m%d", datetime(2018-10-12,"unixepoch", "localtime")))))

How to show the break point instead of assertion error while running the test case in pytest

I am using pytest to validate db data. I am generating a html report which shows test case result,For failed case it only shows Assertion error, But i need the break point where the test case fails. Can anyone help with this?
Main_methods.py
import pymongo
import re
import unittest
import pytest
class Main_methods():
def minlength(self,data,category_name,min_length):
''' validate the minimum length condition by comparing with db data for a given category
Parameter: Db data, category name, minium length '''
for name in data:
len(name[category_name])>=min_length
test_case.py
mport pymongo
import re
import unittest
import pytest
from main_method import Main_methods
class_object=Main_methods()
#pytest.fixture
def data():
'''Initialise the variable for all the methods using this method and returns the value after the yield keyword.'''
myclient = pymongo.MongoClient("mongodb://root:mongodbadmin#18.223.241.113:27017")
mydb = myclient["Ecomm_Product_db"]
mycol = mydb["products"]
yield mycol.find({})
class Test_Category_Name():
def test_minlength(self,data):
assert class_object.minlength(data,'category',5)
Actual result
def test_minlength(self,data):
> assert class_object.minlength(data,'category',5)
E AssertionError: assert None
E + where None = <bound method Main_methods.minlength of <main_method.Main_methods object at 0x0326B670>>(<pymongo.cursor.Cursor object at 0x0346A230>, 'category', 5)
E + where <bound method Main_methods.minlength of <main_method.Main_methods object at 0x0326B670>> = <main_method.Main_methods object at 0x0326B670>.minlength
testcase.py:20: AssertionError
Result i need(Expected)
def test_category_minlength(data):
'''Asserts given min length condition for category name '''
for name in data:
> assert len(name['category'])>=5
E AssertionError: assert 3 >= 5
E + where 3 = len('SSD')

python class managing in another class

I have a class called martCrawler which import other 3 crawlers from other files
However, the code becomes too long as the crawler imported increased.
Is there a better practice managing the code like this?
Thanks in advance~
from Scripts_mart.wat_ver2 import crawler_watsons
from Scripts_mart.cosmed_ver1 import crawler_cosmed
from Scripts_mart.pxmart_ver1 import crawler_pxmart
import datetime
class martCrawler():
def __init__(self):
self.wat = crawler_watsons()
self.cos = crawler_cosmed()
self.pxm = crawler_pxmart()
def crawler_testing(self):
result_pack = {}
wat_result = self.wat.run_before_insert()
cos_result = self.cos.run_before_insert()
pxm_result = self.pxm.run_before_insert()
result_pack['wat'] = wat_result
result_pack['cos'] = cos_result
result_pack['pxm'] = pxm_result
return result_pack
...
Then why not store all the crawlers in a dict from the beginning?
As an example:
from Scripts_mart.wat_ver2 import crawler_watsons
from Scripts_mart.cosmed_ver1 import crawler_cosmed
from Scripts_mart.pxmart_ver1 import crawler_pxmart
class MartCrawler():
def __init__(self, *args):
self.crawlers = {}
for crawler in args:
# use some introspection to get the name of the classes.
# The names should be "crawler_watsons", etc
self.crawlers[crawler.__name__] = crawler()
def crawler_testing(self):
result_pack = {}
for name, crawler in self.crawlers.items():
result_back[name] = crawler.run_before_insert()
return result_back
martCrawler = MartCrawler(crawler_watsons, crawler_cosmed, crawler_pxmart)
Just have in mind that the names in your dict will be the actual names of the imported classes, not 'wat', 'pos' and 'pxm'. But if this is a problem you can use some string manipulation and/or regexes to fix it.
For example you could replace crawler.__name__ with crawler.__name__[8:11]
Just put them into a dictionary:
from Scripts_mart.wat_ver2 import crawler_watsons
from Scripts_mart.cosmed_ver1 import crawler_cosmed
from Scripts_mart.pxmart_ver1 import crawler_pxmart
import datetime
class martCrawler():
def __init__(self):
self.crawlers = {
"wat": crawler_watsons(),
"cos": crawler_cosmed(),
"pxm": crawler_pxmart()
}
def crawler_testing(self):
result_pack = {}
for key in self.crawlers:
result_pack[key] = self.crawlers[key].run_before_insert()
return result_pack

key error while trying to pass data as a parameter using a json file

code is given below
import time
import unittest
import logging as log
from loggenerator import set_log_params,move_latest_log_to_persistent_file
from parse_test_json import get_test_params
from scrapping import SC
class ScrapeTest(unittest.TestCase):
def setup(self):
self.start_time=time.time()
def teardown(self):
self.end_time=time.time()
def test_SC_scrape(self):
try:
test_name="test scrape"
set_log_params(log_file=test_name,level=log.INFO)
log.info("step1:set all test params")
test_params = get_test_params(self.__class__.__name__, test_name=test_name)
log.info("step 2:set")
ik=SC()
log.info("step3:calling scrapper for")
log.debug(ik.parseURL(party_name=test_params["party_name"],start_date=test_params["start_date"],end_date=test_params["end_date"]))
except Exception as e:
raise e
move_latest_log_to_persistent_file(log_file=test_name)
####
import json, os
from builtins import *
def get_test_params(test_class_name = None, test_name = None):
dir_path = os.path.dirname(os.path.realpath(__file__))
file_path = "/test_param_jsons/" + test_class_name + "params.json"
json_file = dir_path + file_path
with open(json_file) as test_data:
test_json = json.load(test_data)
return test_json[test_class_name][test_name]
this function is raising error key error.
this should work as long as you have a json file available at: <SCRIPT_PATH>/test_param_jsons/MyClass_params.json
Also, in order to avoid KeyError you'll need to ensure that your input json file contains key: value, test_class_name : test_name
import json, os
from builtins import *
class MyClass:
def get_test_params (self, test_class_name = None, test_name = None):
with open(os.path.join(os.path.dirname(__file__),"test_param_jsons\\params.json"), 'r') as test_data:
test_json = json.load (test_data)
try:
return test_json[test_class_name][test_name]
except KeyError as e:
print ('KeyError: {}'.format (e))
def mymain(self, test_name):
''' mymain is defined to accomodate your requirement to use __class__ as a parameter '''
test_params = self.get_test_params (self.__class__.__name__, test_name = test_name)
return test_params
if __name__ == '__main__':
test_name = 'sth'
myclass = MyClass ()
result = myclass.mymain (test_name)
print (result)

Django unittest and mocking the requests module

I am new to Mock and am writing a unit test for this function:
# utils.py
import requests
def some_function(user):
payload = {'Email': user.email}
url = 'http://api.example.com'
response = requests.get(url, params=payload)
if response.status_code == 200:
return response.json()
else:
return None
I am using Michael Foord's Mock library as part of my unit test and am having difficulty mocking the response.json() to return a json structure. Here is my unit test:
# tests.py
from .utils import some_function
class UtilsTestCase(unittest.TestCase):
def test_some_function(self):
with patch('utils.requests') as mock_requests:
mock_requests.get.return_value.status_code = 200
mock_requests.get.return_value.content = '{"UserId":"123456"}'
results = some_function(self.user)
self.assertEqual(results['UserId'], '123456')
I have tried numerous combinations of different mock settings after reading the docs with no luck. If I print the results in my unit test it always displays the following instead of the json data structure I want:
<MagicMock name=u'requests.get().json().__getitem__().__getitem__()' id='30315152'>
Thoughts on what I am doing wrong?
Patch json method instead of content. (content is not used in some_function)
Try following code.
import unittest
from mock import Mock, patch
import utils
class UtilsTestCase(unittest.TestCase):
def test_some_function(self):
user = self.user = Mock()
user.email = 'user#example.com'
with patch('utils.requests') as mock_requests:
mock_requests.get.return_value = mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"UserId":"123456"}
results = utils.some_function(self.user)
self.assertEqual(results['UserId'], '123456')
Another pattern I like to use that is a little more reusable would be to start the patcher in your unit test's setUp method. It's also important to check that mock request was called with the expected parameters:
class UtilsTestCase(TestCase):
def setUp(self):
self.user = Mock(id=123, email='foo#bar.com')
patcher = patch('utils.requests.get')
self.mock_response = Mock(status_code=200)
self.mock_response.raise_for_status.return_value = None
self.mock_response.json.return_value = {'UserId': self.user.id}
self.mock_request = patcher.start()
self.mock_request.return_value = self.mock_response
def tearDown(self):
self.mock_request.stop()
def test_request(self):
results = utils.some_function(self.user)
self.assertEqual(results['UserId'], 123)
self.mock_request.assert_called_once_with(
'http://api.example.com'
payload={'Email': self.user.email},
)
def test_bad_request(self):
# override defaults and reassign
self.mock_response.status_code = 500
self.mock_request.return_value = self.mock_response
results = utils.some_function(self.user)
self.assertEqual(results, None)
self.mock_request.assert_called_once_with(
'http://api.example.com'
payload={'Email': user.email},
)
Another way that I believe is more clear and straight forward:
import unittest
from mock import Mock, patch
import utils
class UtilsTestCase(unittest.TestCase):
def test_some_function(self):
mock_response = Mock()
mock_response.status_code = 200
mock_response.json.return_value = {"UserId": "123456"}
with patch('utils.requests.get') as mock_requests:
results = utils.some_function(self.user)
self.assertEqual(results['UserId'], '123456')

Categories

Resources