Using webapp2 i18n in unit tests - unit-testing

I'm using webapp2 with webapp2_extras.i18n for a Google App Engine app.
I have a unit test script as described on the bottom here: https://developers.google.com/appengine/docs/python/tools/localunittesting
The test script imports the models and does not include webapp2 handlers, because the target of the test is the business logic code, not the requests and responses. However, some of my models will call i18n functions like format_currency or gettext which will result in an error:
AssertionError: Request global variable is not set.
How can I initialize the i18n module without instantiating a webapp2 app and request?

I had the same problem (but for uri_for) and I ended up doing the following in my test :
app = webapp2.WSGIApplication(
[webapp2.Route('/', None, name='upload_handler')])
request = webapp2.Request({'SERVER_NAME':'test', 'SERVER_PORT':80,
'wsgi.url_scheme':'http'})
request.app = app
app.set_globals(app=app, request=request)
# call function that uses uri_for('upload_handler')
I had to do trial and error to guess which environment variables have to be set in the request. Maybe you'll need to add more in order to call i18n.

Try to mock your functions.
Example: I have a script called users that import i18n like this:
from webapp2_extras.i18n import gettext as _
So on my tests i mock the function like this:
from pswdless.model import PswdUserEmail, EmailUserArc
from pswdless.users import FindOrCreateUser
from pswdless import users
# mocking i18n
users._ = lambda s: s
#your tests bellow
You can use same trick with another functions.
I hope it helps you.

It seems quite simple to mock i18n itself. I'd prefer this approach because Request and app are really not needed in unit-tests.
Here's a sample pytest fixture:
#pytest.fixture
def mock_i18n(monkeypatch):
class MockI18n:
def set_locale(self, locale):
pass
def gettext(self, string, **variables):
return string
mock_i18n = MockI18n()
def mock_get_i18n(factory=None, key=None, request=None):
return mock_i18n
from webapp2_extras import i18n
monkeypatch.setattr(i18n, 'get_i18n', mock_get_i18n)
yield

Mocking does seem to be the way to go here, but the other answers are not complete and/or more complicated than necessary. Here is a simple mock that works for me.
=== my_module.py ===
from webapp2_extras.i18n import gettext as _
def f(x):
return _(x)
=== test_my_module.py ===
import my_module
def _mock(x):
return x
#mock.patch("my_module._", side_effect=_mock)
def test_f(self, foo):
y = my_module.f("hello")
self.assertEqual(y, "hello")

Related

Mock an internal object call in a Flask view - with pytest

Based on the following example:
app = Flask(__name__)
#app.route('/users')
def get_users():
return UsersAPI().get_users()
And the following tests (using pytest and pytest-mock):
#pytest.fixture
def users():
return UsersAPI(how_many=1)
def test_simple(users, mocker):
mocker.patch("???", return_value=users)
I simply want to call UsersAPI(how_many=1) instead of UsersAPI(). Is this possible to do?
(if you know how to get done with unittest.mock that is also fine since pytest-mock is simply some pytest wrapper)
Turns out it is as easy as:
#pytest.fixture
def users():
return UsersAPI(how_many=1)
def test_simple(users, mocker):
mocker.patch("path.to.module.UsersAPI", return_value=users)
And this also works:
mocker.patch.object(path.to.module, 'UsersAPI', return_value=users)

Why does mock patch only work when running specific test and not whole test suite?

I'm using Django and Pytest specifically to run the test suite and am trying to test that a specific form shows up with expected data when a user hits the site (integration test).
This particular view uses a stored procedure, which I am mocking since the test would never have access to that.
My test code looks like this:
#test_integrations.py
from my_app.tests.data_setup import setup_data, setup_sb7_data
from unittest.mock import patch
...
# Setup to use a non-headless browser so we can see whats happening for debugging
#pytest.mark.usefixtures("standard_browser")
class SeniorPageTestCase(StaticLiveServerTestCase):
"""
These tests surround the senior form
"""
#classmethod
def setUpClass(cls):
cls.host = socket.gethostbyname(socket.gethostname())
super(SeniorPageTestCase, cls).setUpClass()
def setUp(self):
# setup the dummy data - this works fine
basic_setup(self)
# setup the 'results'
self.sb7_mock_data = setup_sb7_data(self)
#patch("my_app.utils.get_employee_sb7_data")
def test_senior_form_displays(self, mock_sb7_get):
# login the dummy user we created
login_user(self, "futureuser")
# setup the results
mock_sb7_get.return_value = self.sb7_mock_data
# hit the page for the form
self.browser.get(self.live_server_url + "/my_app/senior")
form_id = "SeniorForm"
# assert that the form displays on the page
self.assertTrue(self.browser.find_element_by_id(form_id))
# utils.py
from django.conf import settings
from django.db import connections
def get_employee_sb7_data(db_name, user_number, window):
"""
Executes the stored procedure for getting employee data
Args:
user_number: Takes the user_number
db (db connection): Takes a string of the DB to connect to
Returns:
"""
cursor = connections[db_name].cursor()
cursor.execute(
'exec sp_sb7 %s, "%s"' % (user_number, window.senior_close)
)
columns = [col[0] for col in cursor.description]
results = [dict(zip(columns, row)) for row in cursor.fetchall()]
return results
# views.py
from myapp.utils import (
get_employee_sb7_data,
)
...
###### Senior ######
#login_required
#group_required("user_senior")
def senior(request):
# Additional Logic / Getting Other Models here
# Execute stored procedure to get data for user
user_number = request.user.user_no
results = get_employee_sb7_data("production_db", user_number, window)
if not results:
return render(request, "users/senior_not_required.html")
# Additional view stuff
return render(
request,
"users/senior.html",
{
"data": data,
"form": form,
"results": results,
},
)
If I run this test itself with:
pytest my_app/tests/test_integrations.py::SeniorPageTestCase
The tests pass without issue. The browser shows up - the form shows up with the dummy data as we would expect and it all works.
However, if I run:
pytest my_app
All other tests run and pass - but all the tests in this class fail because it's not patching the function.
It tries to call the actual stored procedure (which fails because it's not on the production server yet) and it fails.
Why would it patch correctly when I call that TestCase specifically - but not patch correctly when I just run pytest on the app or project level?
I'm at a loss and not sure how to debug this very well. Any help is appreciated
So what's happening is that your views are imported before you're patching.
Let's first see the working case:
pytest imports the test_integrations file
the test is executed and patch decorator's inner function is run
there is no import of the utils yet and so patch imports and replaces the function
test body is executed, which passes a url to the test client
the test client imports the resolver and in turn it imports the views, which imports the utils.
Since the utils are already patched, everything works fine
If another test case runs first, that also imports the same views, then that import wins and patch cannot replace the import.
Your solution is to reference the same symbol. So in test_integrations.py:
#patch("myapp.views.get_employee_sb7_data")

How to mock redis for Django tests

I am trying to mock out redis in my Django application. I have tried several different methods but none seem to work. What am I doing wrong?
My primary redis instance is called with:
redis_client = redis.from_url(os.environ.get("REDIS_URL"))
That instance is imported in other parts of the app in order to add and retrieve data.
In my tests I tried doing:
import fakeredis
from mock import patch
class TestViews(TestCase):
def setUp(self):
redis_patcher = patch('redis.Redis', fakeredis.FakeRedis)
self.redis = redis_patcher.start()
self.redis.set('UPDATE', 'Spring')
print(redis_client.get('UPDATE'))
def tearDown(self):
self.redis_patcher.stop
When running the tests I want the 'UPDATE' variable to be set. But instead every instance of redis_client fails saying the server is not available. How can I mock out redis and set values, so that they are available when testing my app?
You should mock an item where it is used, not where it came from.
So if redis_client is used in a view like this:
myapp/views.py
from somemodule import redis_client
def some_view_that_uses_redis(request):
result = redis_client(...)
Then in your TestViews you should patch redis_client like this:
class TestViews(TestCase):
def setUp(self):
redis_patcher = patch('myapp.views.redis_client', fakeredis.FakeRedis)
self.redis = redis_patcher.start()

how to mock a method decorated with Python Flask route()

I need to unit test whether a method decorated by a Flask route() gets called or not.
I'd like to do this without modifying the original code under test, if possible, so mocking the method would suite my requirements perfectly.
Hence I am asking this specific question about how to mock a decorated request method (I want to stress this to try to avoid people wasting their time with less specific answers)...
Sample application jflask.py:
from flask import Flask
app = Flask(__name__)
app.config.from_object(__name__)
#app.route('/hello') # This method represents the code under test.
def hello(): # I want to assert that this method gets
return 'Hello, World' # called without modifying this code.
if __name__ == "__main__":
app.run()
In the unit test I'm using #patch() to mock the method so I can assert it was called, but the assertion fails. I.e. the mock method doesn't get called, when I expect it to.
Sample unit test test_hello.py:
import unittest
import jflask
from unittest.mock import patch
class jTest(unittest.TestCase):
def setUp(self):
#jflask.app.testing = True
self.app = jflask.app.test_client()
#patch('jflask.hello') # mock the hello() method
def test_hello(self, mock_method):
rv = self.app.get('/hello')
mock_method.assert_called() # this assertion fails
What am I doing wrong ?
Background
Some background information about the actual behaviour I'm trying to test
(since the above is just a condensed test case, and may not seem entirely sane by itself).
In the actual code I am unit testing, there is a before_request() handler
installed for the app. This gets called by Flask before each request is handled, and in
certain situations this handler has been designed to return a response value, which
causes Flask request processing to stop (in this application's case, this feature is used to centrally validate request parameters), so that the usual routed request handler will (deliberately) not get called.
My unit tests need to assert that request processing gets stopped
or continues, appropriately depending on the situation.
Hence, my test needs to mock the real request handler and assert whether
it was called or not.
This is a little hacky but you could inject a logger.
#app.route(...):
def hello(logger=None):
logger = logger or self.logger
logger.info(...)
return ...
def test_...(self):
logger = MagicMock()
self.app.get(logger)
self.assertTrue(logger.info.called)
from functools import wraps
import logging
from datetime import datetime
logging.basicConfig(filename=datetime.now().strftime('%d_%m_%Y.log'),level=logging.INFO)
def logger_required(f):
#wraps(f)
def decorated(*args, **kwargs):
logging.info(f.__name__ + ' was called')
return f(*args, **kwargs)
return decorated
#app.route('/hello')
#logger_required
def hello(): # I want to assert that this gets called
return 'Hello, World'

Why don't my Django unittests know that MessageMiddleware is installed?

I'm working on a Django project and am writing unittests for it. However, in a test, when I try and log a user in, I get this error:
MessageFailure: You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware
Logging in on the actual site works fine -- and a login message is displayed using the MessageMiddleware.
In my tests, if I do this:
from django.conf import settings
print settings.MIDDLEWARE_CLASSES
Then it outputs this:
('django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware')
Which appears to show the MessageMiddleware is installed when tests are run.
Is there an obvious step I'm missing?
UPDATE
After suggestions below, it does look like it's a settings thing.
I currently have settings/__init__.py like this:
try:
from settings.development import *
except ImportError:
pass
and settings/defaults.py containing most of the standard settings (including MIDDLEWARE_CLASSES). And then settings.development.py overrides some of those defaults like this:
from defaults import *
DEBUG = True
# etc
It looks like my dev site itself works fine, using the development settings. But although the tests seem to load the settings OK (both defaults and development) settings.DEBUG is set to False. I don't know why, or whether that's the cause of the problem.
Django 1.4 has a expected behavior when you create the request with RequestFactory that can trigger this error.
To resolve this issue, create your request with RequestFactory and do this:
from django.contrib.messages.storage.fallback import FallbackStorage
setattr(request, 'session', 'session')
messages = FallbackStorage(request)
setattr(request, '_messages', messages)
Works for me!
A way to solve this quite elegant is to mock the messages module using mock
Say you have a class based view named FooView in app named myapp
from django.contrib import messages
from django.views.generic import TemplateView
class FooView(TemplateView):
def post(self, request, *args, **kwargs):
...
messages.add_message(request, messages.SUCCESS, '\o/ Profit \o/')
...
You now can test it with
def test_successful_post(self):
mock_messages = patch('myapp.views.FooView.messages').start()
mock_messages.SUCCESS = success = 'super duper'
request = self.rf.post('/', {})
view = FooView.as_view()
response = view(request)
msg = _(u'\o/ Profit \o/')
mock_messages.add_message.assert_called_with(request, success, msg)
In my case (django 1.8) this problem occurs in when unit-test calls signal handler for user_logged_in signal, looks like messages app has not been called, i.e. request._messages is not yet set. This fails:
from django.contrib.auth.signals import user_logged_in
...
#receiver(user_logged_in)
def user_logged_in_handler(sender, user, request, **kwargs):
...
messages.warning(request, "user has logged in")
the same call to messages.warning in normal view function (that is called after) works without any issues.
A workaround I based on one of the suggestions from https://code.djangoproject.com/ticket/17971, use fail_silently argument only in signal handler function, i.e. this solved my problem:
messages.warning(request, "user has logged in",
fail_silently=True )
Do you only have one settings.py?
Tests create custom (tests) database. Maybe you have no messages there or something... Maybe you need setUp() fixtures or something?
Need more info to answer properly.
Why not simply do something like ? You sure run tests in debug mode right?
# settings.py
DEBUG = True
from django.conf import settings
# where message is sent:
if not settings.DEBUG:
# send your message ...
This builds on Tarsis Azevedo's answer by creating a MessagingRequest helper class below.
Given say a KittenAdmin I'd want to get 100% test coverage for:
from django.contrib import admin, messages
class KittenAdmin(admin.ModelAdmin):
def warm_fuzzy_method(self, request):
messages.warning(request, 'Can I haz cheezburger?')
I created a MessagingRequest helper class to use in say a test_helpers.py file:
from django.contrib.messages.storage.fallback import FallbackStorage
from django.http import HttpRequest
class MessagingRequest(HttpRequest):
session = 'session'
def __init__(self):
super(MessagingRequest, self).__init__()
self._messages = FallbackStorage(self)
def get_messages(self):
return getattr(self._messages, '_queued_messages')
def get_message_strings(self):
return [str(m) for m in self.get_messages()]
Then in a standard Django tests.py:
from django.contrib.admin.sites import AdminSite
from django.test import TestCase
from cats.kitten.admin import KittenAdmin
from cats.kitten.models import Kitten
from cats.kitten.test_helpers import MessagingRequest
class KittenAdminTest(TestCase):
def test_kitten_admin_message(self):
admin = KittenAdmin(model=Kitten, admin_site=AdminSite())
expect = ['Can I haz cheezburger?']
request = MessagingRequest()
admin.warm_fuzzy_method(request)
self.assertEqual(request.get_message_strings(), expect)
Results:
coverage run --include='cats/kitten/*' manage.py test; coverage report -m
Creating test database for alias 'default'...
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Destroying test database for alias 'default'...
Name Stmts Miss Cover Missing
----------------------------------------------------------------------
cats/kitten/__init__.py 0 0 100%
cats/kitten/admin.py 4 0 100%
cats/kitten/migrations/0001_initial.py 5 0 100%
cats/kitten/migrations/__init__.py 0 0 100%
cats/kitten/models.py 3 0 100%
cats/kitten/test_helpers.py 11 0 100%
cats/kitten/tests.py 12 0 100%
----------------------------------------------------------------------
TOTAL 35 0 100%
This happened to me in the login_callback signal receiver function when called from a unit test, and the way around the problem was:
from django.contrib.messages.storage import default_storage
#receiver(user_logged_in)
def login_callback(sender, user, request, **kwargs):
if not hasattr(request, '_messages'): # fails for tests
request._messages = default_storage(request)
Django 2.0.x
I found when I had a problem patching messages the solution was to patch the module from within the class under test (obsolete Django version BTW, YMMV). Pseudocode follows.
my_module.py:
from django.contrib import messages
class MyClass:
def help(self):
messages.add_message(self.request, messages.ERROR, "Foobar!")
test_my_module.py:
from unittest import patch, MagicMock
from my_module import MyClass
class TestMyClass(TestCase):
def test_help(self):
with patch("my_module.messages") as mock_messages:
mock_messages.add_message = MagicMock()
MyClass().help() # shouldn't complain about middleware
If you're seeing a problem in your Middleware, then you're not doing "Unit Test". Unit tests test a unit of functionality. If you interact with other parts of your system, you're making something called "integration" testing.
You should try to write better tests, and this kind of problems shouldn't arise. Try RequestFactory. ;)
def test_some_view(self):
factory = RequestFactory()
user = get_mock_user()
request = factory.get("/my/view")
request.user = user
response = my_view(request)
self.asssertEqual(status_code, 200)