Django Sessions in StaticLiveServerTestCase

This is for Django 1.8.

Here was the scenario. I have a Django site where most of the pages require the user to login. I have a form that gets some initial values from Django sessions. The form has lots of javascript, so live testing seemed best. The problem I could not set the session values in the test code.

Initially, this seemed pretty easy because in the test code, there was a variable called:

self.client.session

However, changes to the session were not present when the test code called the view. Careful inspection showed that the session id’s in self.client.session and view.request.session were different.

Adding the following method to StaticLiveServerTestCase solved the problem:

    def save_to_session(self, key, value):
        cookies = self.selenium.get_cookies()
        session_key = None
        for cookie in cookies:
            if cookie[u'name'] == u'sessionid':
                session_key = cookie[u'value']
                break

        if session_key:
            from django.contrib.sessions.backends.cached_db import SessionStore
            s = SessionStore(session_key)
            s[key] = value
            s.save()

 

Advertisements

Django and Pytest

As of Django 1.8, when you run tests and have migrations, the tests build the test database from the migrations (if you do not believe me, run tests with -v 3). If you have a lot of migrations, this can be painfully slow.

Pytest has an option that builds the test database directly:

pytest --nomigrations ./my_test.py

In my case, I get about a x5 speed up.

This works for  django.test.TestCase and django.contrib.staticfiles.testing.StaticLiveServerTestCase.

Unfortunately, pytest is not a drop in replacement for django test.

Failed: Database access not allowed, use the “django_db” mark

Sometimes you will get the above error. Based on the error message, it seems like you may need to alter your code some how. However, according to the docs, if you are using django.test.TestCase you do not need to alter your code.

One way you get get this error is if your imports access the database. Move the imports to inside the test class and the problems go away.

Python Mock Functions Not Being Called

Are you using mock to mock some functions during testing and mysteriously your tests are ignoring the mock decorator?

One way this can happen is if the function being mocked is either defined in the module that is being tested or is imported into the module being tested. How to get around this?

In my case, after I wasted time figuring out why mock was not working, I ended up modifying the code in my module so that I did not need mock. In the end, I think the modified code is better anyway. Often making code testable, makes it better too.

Testing Django When Using @cached_property

I use the @cached_property decorator quite a bit. It’s pretty straightforward. Usually, it’s set it and forget it. As noted in the docs, it persists as long as the instance persists.

However, it can cause problems during testing if you want to change it’s value. Here is how to change the value (from SO):

class SomeClass(object):

    @cached_property
    def expensive_property(self):
         return datetime.now()

obj = SomeClass()
print obj.expensive_property
print obj.expensive_property # outputs the same value as before
del obj.expensive_property
print obj.expensive_property # outputs new value

Django Tests, Selenium, Ajax and PyCharm

If you need to test Django code that involves Ajax, Selenium is the way to go. If you use PyCharm, you probably use the debugger all the time. In fact, you might be inclined to put some break points in the Ajax callbacks and inspect some variables when execution stops at those break points. All pretty straight forward… except if your callback accesses the database.

When the debugger stops in the callback, it turns out it cannot access the test database. If your callback accesses the database, then what the debugger is showing you is not what is actually happening. I ended up solving the problem I was having by putting print statements in the callback.

Django, Selenium Headless Tests Fail

Running headless selenium tests is great. Except when the tests succeed when headed, then fail when headless. When that happens it’s hard not to let your imagination run wild with crazy javascript thoughts.

There are many ways to run headless. The method I use is described in this post. In a sense this is not headless because a normal browser is running; it’s just running on a hidden display. This makes the headless fails all the more perplexing.

In this case, the problem was the headless browser window was a different size compared to the headed version. I use the chosen widget a lot. It’s a fancy combo of an HTML select widget and auto-complete. Clicking on this widget creates a pop-up drop down. If the needed choice is outside of the browser, selenium auto scrolls the page so that the choice is visible. When it decides to scroll depends on the browser window dimensions.

In many cases, this auto scroll would no create problems. In my case, I also have a nav-bar locked to the top of the screen. Sometimes the desired choice would end up under the nav-bar after auto-scroll. When selenium moved to select the choice, it would get the nav bar instead. The form value would not be set and the form would fail. Although it did not happen to me, I supposed it’s possible selenium could have ended up on a different page. That could make some strange errors.

The first part of the fix is to explicitly set the browser window so the headed and headless versions are the same. Something like this:

if getattr(settings, 'HEADLESS_TESTS', False):
    self.vdisplay = Display(visible=False, size=(1600, 1000))
    self.vdisplay.start()
self.selenium = webdriver.Firefox()
self.selenium.set_window_size(1500, 900)

If all the problems go away, you are done. If not, then hopefully the headed tests and headless tests will now fail in the same way, making debugging easier.

In my case, I added code to scroll the page if the item was under the nav bar. Here is the code.