I have an example of class mocking :
worker.py
import os
class Helper:
def __init__(self, path):
self.path = path
def get_folder(self):
base_path = os.getcwd()
return os.path.join(base_path, self.path)
def get_path(self):
return self.path
class Worker:
def __init__(self):
self.helper = Helper('db')
def work(self):
path = self.helper.get_path()
print(f'Working on {path}')
return path
And a test file : test_worker.py
from unittest import TestCase, mock
from worker import Worker, Helper
class TestWorkerModule(TestCase):
def test_patching_class(self):
with mock.patch('worker.Helper') as MockHelper:
MockHelper.return_value.get_path.return_value = 'testing'
worker = Worker()
self.assertEqual(worker.work(), 'testing')
The test returns ok as long as the 2 classes are in the same file. Separating them into 2 files worker_1.py for class Helper and worker_2.py for class Worker, the test fails with :
AssertionError: 'db' != 'testing'
Why ? And how can I correct this behaviour ?
Thanks
Lets say we have two files, worker.py and helper.py
worker.py:
from helper import Helper
class Worker:
def __init__(self):
self.helper = Helper('db')
def work(self):
path = self.helper.get_path()
print('Working on {path}')
return path
helper.py:
import os
class Helper:
def __init__(self, path):
self.path = path
def get_folder(self):
base_path = os.getcwd()
return os.path.join(base_path, self.path)
def get_path(self):
return self.path
What you did here is to import Helper class into worker module,
so when you trying to do that at your test_worker.py:
from unittest import TestCase, mock
from worker import Worker
class TestWorkerModule(TestCase):
def test_patching_class(self):
with mock.patch('helper.Helper') as MockHelper:
MockHelper.return_value.get_path.return_value = 'testing'
worker = Worker()
self.assertEqual(worker.work(), 'testing')
result:
AssertionError: 'db' != 'testing'
- db
+ testing
you are patching the Helper class in helper.py module,
but you already imported the Helper class into worker module, because of that you need to patch the Helper class in worker module, for example:
from unittest import TestCase, mock
from worker import Worker
class TestWorkerModule(TestCase):
def test_patching_class(self):
with mock.patch('worker.Helper') as MockHelper:
MockHelper.return_value.get_path.return_value = 'testing'
worker = Worker()
self.assertEqual(worker.work(), 'testing')
result:
Ran 1 test in 0.002s
OK
Another way to make it work for general use cases (lets say you want to patch Helper class in all modules that use it), it is to change worker.py to that:
import helper
class Worker:
def __init__(self):
self.helper = helper.Helper('db')
def work(self):
path = self.helper.get_path()
print('Working on {path}')
return path
here you are importing helper module, and use Helper class under helper module,
so now your test.py should seem like that:
from unittest import TestCase, mock
from worker import Worker
class TestWorkerModule(TestCase):
def test_patching_class(self):
with mock.patch('helper.Helper') as MockHelper:
MockHelper.return_value.get_path.return_value = 'testing'
worker = Worker()
self.assertEqual(worker.work(), 'testing')
result:
Ran 1 test in 0.001s
OK
Thats worked because instead of importing the Helper class into worker module, we have imported the helper module, so patching "helper.Helper" will now really work correctly.
Related
I am looking for a way to use simple dependency injection in combination with flask-sqlalchemy
Here's an example setup
extensions.py
db = SQLAlchemy()
my_model.py
from extensions import db
class MyModel(db.Model):
id = db.Column(db.Integer, primary_key=True)
// ... some more fields
my_model_repository.py
from my_model import MyModel
class MyModelRepository:
def get_all():
MyModel.query.all()
Now I would like to somehow inject the db into MyModelRepository so that when I unit test it I can inject a mock or an in-memory database without having to deal with any global config or flags.
Here's an example of how I would want it to look
from my_model import MyModel
class MyModelRepository:
def __init__(self, db):
self.db = db
def get_all():
db.query(MyModel).all()
This however seems to not be possible because MyModel inherits from db.Model and db already is a specific instance of SQLAlchemy.
What is the appropriate way to make the database injectable to any component that depends on it?
For me it worked something like this:
In my_model_repository.py add the inject decorator to the init of your class
from my_model import MyModel
from flask_sqlalchemy import SQLAlchemy
class MyModelRepository:
#inject
def __init__(self, db: SQLAlchemy):
self.db = db
def get_all():
db.query(MyModel).all()
and in your app.py or another source file where you create your flask app add:
import flask_injector
from extensions import db
app = Flask(__name__) #something like this should already be in your code
db.init_app(app)
#this function could potentially be moved to it's own file
def configure_dependencies(binder):
binder.bind(SQLAlchemy, to=db, scope=flask_injector.singleton)
flask_injector.FlaskInjector(app=app, modules=[configure_dependencies])
Data sources:
https://pypi.org/project/Flask-Injector/
https://www.pythonfixing.com/2022/04/fixed-circular-import-of-db-reference.html
I'm using Django 3.0.5, pytest 5.4.1 and pytest-django 3.9.0. I want to create a fixture that returns a User object to use in my tests.
Here is my conftest.py
import pytest
from django.contrib.auth import get_user_model
#pytest.fixture
def create_user(db):
return get_user_model().objects.create_user('user#gmail.com', 'password')
Here is my api_students_tests.py
import pytest
from rest_framework.test import APITestCase, APIClient
class StudentViewTests(APITestCase):
user = None
#pytest.fixture(scope="session")
def setUp(self, create_user):
self.user = create_user
def test_create_student(self):
assert self.user.email == 'user#gmail.com'
# other stuff ...
I keep getting the following error
Fixture "setUp" called directly. Fixtures are not meant to be called directly,
but are created automatically when test functions request them as parameters.
I read and read again this previous question but I cannot find out a solution. Furthermore, in that question the fixture wasn't returning nothing, while in my case it should return an object (don't know if it can make any difference)
Just skip the setUp:
#pytest.fixture(scope='session')
def create_user(db):
return get_user_model().objects.create_user('user#gmail.com', 'password')
class StudentViewTests(APITestCase):
def test_create_student(self, create_user):
assert user.email == 'user#gmail.com'
# other stuff ...
I write testing code using flask_testing
following is my testing code
from app import create_app, db
class SampleTest(TestCase):
def create_app(self):
self.db_fd, self.db_path = tempflie.mkstemp()
return create_app({'DATABASE': self.db_path})
def setUp(self):
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
os.close(self.db_fd)
os.unlink(self.db_path)
def test1(self):
response = self.get('/test1/')
def test2(self):
response = self.get('/test2/')
when I debug the test code, I found that create_app is called in all test functions, including test1, test.
how can i call create_app function only once?
def create_app(self):
self.db_fd, self.db_path = tempflie.mkstemp()
return create_app({'DATABASE': self.db_path})
This is confusing and I suspect not intended you import create_app and have a method called create_app.
Also do you have a #pytest.fixture on your imported create_app?
I am trying to create a timeout wrapper for a unittest class using this question as a template:
PyUnit - How to unit test a method that runs into an infinite loop for some input?
The above solution works well if running from Main, but I'm trying to create a similar timeout by using a unittest class, but it fails.
PicklingError: Can't pickle : it's not found as TimeTest.test1
I'm open to any other solutions that are platform independent and work with the native 2.7 library
Here is the code
import unittest
import multiprocessing as mp
import functools as ft
import time
def timeout(seconds=5, error_message="Timeout"):
def decorator(func):
def wrapper(*args, **kwargs):
process = mp.Process(None, func, None, args, kwargs)
process.start()
process.join(seconds)
if process.is_alive():
process.terminate()
raise mp.TimeoutError(error_message)
return ft.wraps(func)(wrapper)
return decorator
class TestCases(unittest.TestCase):
def setUp(self):
self.a = 0
#timeout(1)
def test1(self):
time.sleep(2)
self.assertEquals(self.a, 0)
I ended up ditching the wrapper idea and creating a simple function and assert.
import unittest
import multiprocessing as mp
import time
TIME_LIMIT = 1
class TestCases(unittest.TestCase):
def setUp(self):
self.a = 0
def my_func(self):
time.sleep(2)
self.q.put(self.a + 1)
def run_case(self, func):
self.q = mp.Queue()
test_process = mp.Process(target=func)
test_process.start()
test_process.join(TIME_LIMIT)
self.assertFalse(test_process.is_alive(), 'timeout exceeded')
def test_case1(self):
self.run_case(self.my_func)
self.assertEquals(self.a + 1, self.q.get())
I'm trying to test the response code of a view, but I'm either getting a 301 or does not exist.
urls.py
...
url(r'^myview/(?P<view_id>.*)/$', view_myview.index, name='myview'),
...
Test code 1:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123')
self.assertEqual(response.status_code, 200)
The above code gives:
AssertionError: 301 != 200
Test code 2:
import unittest
from django.test import Client
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
def test_details(self):
response = self.client.get('/myview/123/')
self.assertEqual(response.status_code, 200)
The above code gives:
Mymodel matching query does not exist.
All I want to do is simple testing of my views to ensure they aren't throwing an error code, but I can't seem to find the right way to do it and I've tried many, many suggestions from the internets. Is there a different way to pass in view_id? What if I also want to throw in some query parameters?
EDIT: Updating to show the workaround I've used to accomplish what I'm trying to do, as horrible as it may be. I found that using dumpdata and fixtures took FOREVER.
from django.test import TestCase
from django.test import Client
import os
from . import urls_to_test # just a simple list of strings
class SimpleTest(TestCase):
""" Simply test if views return status 200 """
def setUp(self):
self.client = Client()
print('Dumping production database...')
os.system("sudo mysqldump mydb > /tmp/mydb.sql")
print('Loading production data into test database...')
os.system("sudo mysql test_mydb < /tmp/mydb.sql")
os.system("sudo rm -rf /tmp/mydb.sql")
def test_details(self):
for u in urls_to_test.test_urls:
print('Testing {}'.format(u))
response = self.client.get(u)
self.assertEqual(response.status_code, 200)
print('{} URLs tested!'.format(len(urls_to_test.test_urls)))
The first one doesn't work because Django is redirecting to the version with a final slash.
The second one tells you exactly why it doesn't work: you haven't created an item with id 123 - or indeed any items at all - within the test.
Consider creating object before testing its existance:
import unittest
from django.test import Client
from app.models import YourModel
class SimpleTest(unittest.TestCase):
def setUp(self):
self.client = Client()
self.obj = YourModel.objects.create(*your object goes here*)
def test_details(self):
response = self.client.get('/myview/123/') # It may be not /123/. It depends on how you generate url for model
self.assertEqual(response.status_code, 200)