Django: How to mock a method called in an api test? - django

I keep hitting a method scan_file() that should be mocked. It happens while calling self.client.post() in a django api test. The app setup is below, I've tried to mock the imported scan_file patch("myapp.views.scan_file") as well as the source location patch("myapp.utils.scan_file") neither work.
# myapp.views.py
from myapp.utils import scan_file
class MyViewset():
def scan():
scan_file(file) # <- this should be mocked but its entering the code
#myapp.utils.py
def scan_file(file) -> bool:
boolean_result = call_api(file)
return boolean_result
#test_api.py
class MyAppTest():
def test_scan_endpoint(self):
patcher = patch("myapp.views.scan_file")
MockedScan = patcher.start()
MockedScan.return_value = True
# This post hits the scan_file code during the api
# call but it should be mocked.
resp = self.client.post(
SCAN_ENDPOINT,
data={
"file" :self.text_file,
"field1" : "Progress"
}
)
I've also tried the following syntax for mocking, and tried including it in the test setup() as well:
self.patcher = patch("myapp.views.scan_file")
self.MockedScan = self.patcher.start()
self.MockedScan.return_value = True

Related

Django rest framework: unit testing database issue

I am doing unit testing of the rest Apis. I am using django rest framework.
Apis are saving data into and getting data from the database. Both of the operations are not working or if it is working i am not able to see that in the databases. Apis are also using django-fsm, because of which i need same data from the db for the other tests. Tests depends on previous tests due to django-fsm. There is always state changing with the api. But now i am not able to see any data in database during test runs. Don't know where it is saving the data or in which database.
Below is my test settings:-
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': join(PROJECT_ROOT, 'run', 'db_for_testing.sqlite3'),
'TEST': {
'NAME': 'test_db_for_testing',
},
},
}
below is my api:-
class DummyView(CreateAPIView):
def post(self, request, *args, **kwargs):
data = request.data.copy()
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
order = self.model(book=serializer.data.get('book'))
order.save()
data = {
'status_code': 200,
'message': 'successfully.'
}
return Response(data, status=status.HTTP_200_OK)
As my tests depends on the previous test saving the data to db, so the other tests also fails. I am using APITestCase of rest_framework.
Help guys.
thanks in advance.
If I'm understanding your question correctly, Django "clear" database after each test (either rolling back or truncating.) So you need to write your tests accordingly.
See: https://docs.djangoproject.com/en/1.10/topics/testing/tools/#transactiontestcase
TL;DR - Solution: Use SimpleTestCase - See example below
Explanation
The thing is that the recommended test classes provided by Django for tests involving database queries, TransactionTestCase and the subclass TestCase, wraps every test in a transaction to speed up the process of resetting the database after each test. Source: Django TransactionTestCase docs
It is possible to avoid this behaviour by using SimpleTestCase which is the parent class of TransactionTestCase. You then have to specify explicitly that you want to allow database queries by setting allow_database_queries to True-
Also note that you are then responsible for any cleaning that needs to be done after the test. You can do that by overriding the tearDownClass class method. Similarly there's a setUpClass class method for any initialization prior to running the test. Remember to call the super methods. See full details in the docs
Example
from django.test import SimpleTestCase
class MyTestCase(SimpleTestCase):
allow_database_queries = True
#classmethod
def setUpClass(cls):
# Do your pre test initialization here.
super(MyTestCase, cls).setUpClass()
#classmethod
def tearDownClass(cls):
# Do your post test clean uphere.
super(MyTestCase, cls).tearDownClass()
def test_add_data_through_api(self):
# Add 'dat data
...
def test_work_with_data_from_previous_test(self):
# Work 'dat data
...
Use the --keepdb option when calling manage.py test:
https://docs.djangoproject.com/en/2.1/ref/django-admin/#cmdoption-test-keepdb
It's available since django 1.8.
Hope this helps.
Here my three tests are dependent with previous one. If I run them as separate test the previous test data deleted and later one failed for lost of that data.
So I make them in two different functions both are not test function. Finally call the dependent functions from one test function.
class test_profile(APITestCase):
def setUp(self):
super_user = default_service.create_super_user()
self.application = default_service.create_oath2_application(super_user.id)
self.content_type = "application/x-www-form-urlencoded"
self.username = "user#domain.com"
self.password = "pass123"
def create_profile(self):
url = reverse('EmailSignUp')
body = {
"email": self.username,
"password": self.password,
"fullname": "Mamun Hasan"
}
response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))['data']
# print("Profile", data)
self.assertEqual(data['username'], self.username)
self.assertEqual(data['fullname'], data['fullname'])
def get_access_token(self):
url = reverse('oauth2_provider:token')
body = {
"username": self.username,
"password": self.password,
"grant_type": self.application.authorization_grant_type,
"client_id": self.application.client_id,
"client_secret": self.application.client_secret,
}
response = self.client.post(url, body, CONTENT_TYPE=self.content_type)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))
# print("AccessToken", data)
self.assertEqual(data['token_type'], 'Bearer')
return data
def get_profile(self, oath2_token):
url = reverse('GetProfile')
authorization = oath2_token["token_type"] + ' ' + oath2_token["access_token"]
response = self.client.get(url, CONTENT_TYPE=self.content_type, HTTP_AUTHORIZATION=authorization)
self.assertEqual(response.status_code, status.HTTP_200_OK)
data = (json.loads(response.content))['data']
# print("Profile", data)
self.assertEqual(data['username'], self.username)
def test_dependent(self):
self.create_profile()
oath2_token = self.get_access_token()
self.get_profile(oath2_token)
I did not find any solution to commit the previous API data. If anyone knows please comment. So I have done it this way. I don't know this is the best solution but it works and tested.

Integrationtest of scrapy pipeline returning deferred

Is it possible to create a integration test of a scrapy-pipeline? I can't figure out how to do this. In particular I am trying to write a test for the FilesPipeline and I also want it to persist my mocked response to Amazon S3.
Here is my test:
def _mocked_download_func(request, info):
return Response(url=response.url, status=200, body="test", request=request)
class FilesPipelineTests(unittest.TestCase):
def setUp(self):
self.settings = get_project_settings()
crawler = Crawler(self.settings)
crawler.configure()
self.pipeline = FilesPipeline.from_crawler(crawler)
self.pipeline.open_spider(None)
self.pipeline.download_func = _mocked_download_func
#defer.inlineCallbacks
def test_file_should_be_directly_available_from_s3_when_processed(self):
item = CrawlResult()
item['id'] = "test"
item['file_urls'] = ['http://localhost/test']
result = yield self.pipeline.process_item(item, None)
self.assertEquals(result['files'][0]['path'], "full/002338a87aab86c6b37ffa22100504ad1262f21b")
I always run into the following error:
DirtyReactorAggregateError: Reactor was unclean.
How do I create a proper test using twisted and scrapy?
Up do now I did my pipeline tests without the call to from_crawler, so they are not ideal, because they do not test the functionality of from_crawler, but they work.
I do them by using an empty Spider instance:
from scrapy.spiders import Spider
# some other imports for my own stuff and standard libs
#pytest.fixture
def mqtt_client():
client = mock.Mock()
return client
def test_mqtt_pipeline_does_return_item_after_process(mqtt_client):
spider = Spider(name='spider')
pipeline = MqttOutputPipeline(mqtt_client, 'dummy-namespace')
item = BasicItem()
item['url'] = 'http://example.com/'
item['source'] = 'dummy source'
ret = pipeline.process_item(item, spider)
assert ret is not None
(in fact, I forgot to call open_spider())
You can also have a look at how scrapy itself does the testing of pipelines, e.g. for MediaPipeline:
class BaseMediaPipelineTestCase(unittest.TestCase):
pipeline_class = MediaPipeline
settings = None
def setUp(self):
self.spider = Spider('media.com')
self.pipe = self.pipeline_class(download_func=_mocked_download_func,
settings=Settings(self.settings))
self.pipe.open_spider(self.spider)
self.info = self.pipe.spiderinfo
def test_default_media_to_download(self):
request = Request('http://url')
assert self.pipe.media_to_download(request, self.info) is None
You can also have a look through their other unit tests. For me, these are always good inspiration on how to unit test scrapy components.
If you want to test the from_crawler function, too, you could have a look on their Middleware tests. In these tests, they often use from_crawler to create middlewares, e.g. for OffsiteMiddleware.
from scrapy.spiders import Spider
from scrapy.utils.test import get_crawler
class TestOffsiteMiddleware(TestCase):
def setUp(self):
crawler = get_crawler(Spider)
self.spider = crawler._create_spider(**self._get_spiderargs())
self.mw = OffsiteMiddleware.from_crawler(crawler)
self.mw.spider_opened(self.spider)
I assume the key component here is to call get_crawler from scrapy.utils.test. Seems they factored out some calls you need to do in order to have a testing environment.

nose #with_setup settings fail to be used in the test

using nose to test a flask app. I'm trying to use the with_setup decorator to make my test DRY, without having to repeat the setup every test function. but it doesn't seem to run the #with_setup phase. As the docs state I'm using it with test functions and not test classes. some code:
from flask import *
from app import app
from nose.tools import eq_, assert_true
from nose import with_setup
testapp = app.test_client()
def setup():
app.config['TESTING'] = True
RUNNING_LOCAL = True
RUN_FOLDER = os.path.dirname(os.path.realpath(__file__))
fixture = {'html_hash':'aaaa'} #mocking the hash
def teardown():
app.config['TESTING'] = False
RUNNING_LOCAL = False
#with_setup(setup, teardown)
def test_scrape_wellformed_html():
#RUN_FOLDER = os.path.dirname(os.path.realpath(__file__)) #if it is here instead of inside #with_setup the code works..
#fixture = {'html_hash':'aaaa'} #mocking the hash #if it is here the code works
fixture['gush_id'] = 'current.fixed'
data = scrape_gush(fixture, RUN_FOLDER)
various assertions
for example if I create the fixture dict inside the #with_setup block, instead of inside the specific test method (and in everyone of them) I'll get a NameError (or something similar)
I guess I'm missing something, just not sure what.
Thanks for the help!
The issue is that the names RUN_FOLDER and fixture scoped to the function setup and so will not be available to test_scrape_wellformed_html. If you look at the code for with_setup you will see that it does not do anything to alter the run function's environment.
In order to do what you want to do you need to make your fixtures global variables:
testapp = app.test_client()
RUN_FOLDER = os.path.dirname(os.path.realpath(__file__))
fixture = None
def setup():
global fixture
app.config['TESTING'] = True
fixture = {'html_hash':'aaaa'} #mocking the hash
def teardown():
global fixture
app.config['TESTING'] = False
fixture = None
#with_setup(setup, teardown)
def test_scrape_wellformed_html():
# run test here

Django unit test views function - example

I'm newbie in Django tests. How to create Unit Test for this views function? My unit test function should import function from views? Please an example. This will help me to understand how it work
#maintainance_job
def time_to_end(request):
today = datetime.date.today()
datas = Data.objects.filter(start__lte=today,
other_date__gte=today)
for data in datas:
subject = _(u'Send email')
body = render_to_string('mail.txt',
{'data': data})
email = EmailMessage(subject, body,
'admin#admin.com',
[data.user.email])
email.send()
return HttpResponse('Done')
urls:
(r'^maintainance/jobs/time_to_end/$', 'content.views.time_to_end'),
There is a simpliest test for your case (place it in tests.py of a directory where is your view function):
from django.utils import unittest
from django.test.client import Client
class HttpTester( unittest.TestCase ):
def setUp( self ):
self._client = Client() # init a client for local access to pages of your site
def test_time_to_end( self ):
response = self._client.get( '/jobs/time_to_end/' )
# response = self._client.post( '/jobs/time_to_end/' ) - a 'POST' request
result = response.content
assert result != 'Done'
So, we use self._client to make 'get' and 'post' requests. Responses can be accessed by reading response.content (the full text of response) or by reading response.context if you use templates and want to access variables passing to the templates.
For example if your view normally must pass the dict with context variable 'result' to template:
{ 'result': "DONE" }
then you could check your result:
result = response.context[ 'result' ]
assert result != 'Done'
So, you wait your test will have the 'result' variable and it will be 'Done'. Otherwise you raise AssertionError (note assert statement).
If there is an exception then tests fails. AssertionError is an exception too.
More details - in the docs and in a book "Dive into Python".

Mocking Django query using Mox

I'm trying to mock a django filter query using Mox. I am following the instructions on Mox website, however, since my django query is a chained method, it complains that the AndReturn() method doesn't exist.
Here is my method:
def CheckNameUniqueness(device):
ex_device = device.__class__.objects.filter(name__iexact=device.name)
if not ex_device:
return None
if ex_device.count() > 0:
return ex_device
In my unit test, I'm trying to mock the filter method to return an empty list.
class testCheckNameUniqeness(unittest.TestCase):
""" Unit test for CheckNameUniqueness function """
def setUp(self):
self.device_mocker = mox.Mox()
def testCheckNameUniqenessNotExists(self):
device = self.device_mocker.CreateMock(models.Device)
device.name = "some name"
device.objects.filter(name__iexact=device.name).AndReturn(None)
# Put all mocks created by mox into replay mode
self.device_mocker.ReplayAll()
# Run the test
ret = CheckNameUniqueness(device)
self.device_mocker.VerifyAll()
self.assertEqual(None, ret)
When I run my test case, I get the following error:
AttributeError: 'QuerySet' object has no attribute 'AndReturn'
Note that, because of the large number of database tables, oracle database, and other complications, this unit test has to be run without creating database.
Wouldn't it be
device.CheckNameUniqueness().AndReturn(None)
? That's how I read the Mox documentation. I haven't actually used it myself yet though.
I ran into this same problem.
def testCheckNameUniqenessNotExists(self):
self.device_mocker.StubOutWithMock(models.Device, "objects")
models.Device.objects.filter(name__iexact=device.name).AndReturn(None)
# Put all mocks created by mox into replay mode
self.device_mocker.ReplayAll()
# Run the test
ret = CheckNameUniqueness(device)
self.device_mocker.VerifyAll()
self.assertEqual(None, ret)
If you want to chain QuerySets, you can make a mock of a QuerySet and have it be the return:
from django.db.models.query import QuerySet
def testCheckNameUniqenessNotExists(self):
qs = self.device_mocker.CreateMock(QuerySet)
self.device_mocker.StubOutWithMock(models.Device, "objects")
models.Device.objects.filter(name__iexact=device.name).AndReturn(qs)
qs.count().AndReturn(1)
# Put all mocks created by mox into replay mode
self.device_mocker.ReplayAll()
# Run the test
ret = CheckNameUniqueness(device)
# etc...