Write unittests

In the quick start guide we created our first Brython app. Continuing it, let’s create some tests, check if H1 is inserted into DOM, and our code is working properly.

To run client side tests, you need to activate django-brython testing middleware:

# project/settings.py

MIDDLEWARE = [
    # ...
    'django_brython.middleware.BrythonUnittestMiddleware'
]

Create test class, which will run in the browser. Place the testing logic into test_* functions as you do in normal Django testcases.

# frontend/tests/brython.py

from browser import document

class BrythonTest:
    def test_example(self):
        h1 = document.getElementById('main')
        # Force AssertionError
        assert h1.text == 'wrong content'

Start the dev server, and try to open the index page, with the following query string:

http://localhost:8000/backend/index/?__BRYTHON_UNITTEST__=frontend.tests.brython.BrythonTest.test_example

So any view can test against any testcase specified by the full path in HTTP GET parameter. If you check the debug console in your browser, the assertion is shown.

The MIDDLEWARE injekts the testing code at the end of your HTML, and runs it.

Integrate test into Django testing framework

If you would like to run the previously created test when you call,:

python manage.py test

then you need to connect the client side test with Django testing framework.

Firstly create StaticLiveServerTestCase, which runs a browser with Selenium webdriver:

# frontend/tests/test_django.py

from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from selenium import webdriver

from .brython import BrythonTest


class Test1(StaticLiveServerTestCase, BrythonTest):
    def setUp(self):
        self.driver = webdriver.Chrome()

    def tearDown(self):
        self.driver.quit()

This class inherits from LiveServerTestCase and also the previously created BrythonTest class.

Modify the BrythonTest, and decorate the test_* methods:

from browser import document

from django_brython.testing import location, reverse


class BrythonTest:
    @location(reverse('backend:index'))
    def test_example(self):
        h1 = document.getElementById('main')
        assert h1.text == 'wrong content'

This decorator tells to the test runner which URL to open before running the client side Brython tests. Please use django_brython reverse method instead of Django’s builtin.

If you run the tests, the Brython exceptions will shown in the console:

python manage.py test

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
E
======================================================================
ERROR: test_example (frontend.tests.test_django.Test1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/project/django_brython/testing.py", line 64, in dec
    return runner()
  File "/tmp/project/django_brython/testing.py", line 49, in __call__
    raise Exception(test_result['exception'])
Exception: Traceback (most recent call last):
  File "http://localhost:48535/backend/index/?__BRYTHON_UNITTEST__=frontend.tests.brython.BrythonTest.test_example/__main__2", line 9, in <module>
    func()
  File "/brython/django_brython/testing.py", line 11, in dec
    return function(inst)
  File "/brython/frontend/tests/brython.py", line 11, in test_example
    assert h1.textContent == 'wrong content'
AssertionError: AssertionError


----------------------------------------------------------------------
Ran 1 test in 13.104s

FAILED (errors=1)
Destroying test database for alias 'default'...

Race conditions

If you run this test a lot of time, then sometimes you’ll get AttributeError:

brython.js:6308 Traceback (most recent call last):
  File "http://localhost:8000/backend/index/?__BRYTHON_UNITTEST__=frontend.tests.brython.BrythonTest.test_example/__main__2", line 7, in <module>
    func()
  File "/brython/django_brython/testing.py", line 36, in dec
    return function(inst)
  File "/brython/frontend/tests/brython.py", line 12, in test_example
    assert h.text == "wrong content"
AttributeError: text

This effect is because there is some race condittion between the client site codes (frontend/main.py) and the unittests. Each Brython script is loaded with ajax calls, in your client codes can have async functions too. That’s why the scripts running order isn’t predicted.

Solution 1:

Call your business logic from unittest. Modify the main.py, create a function:

# frontend/main.py

from browser import document, html

def display():
    print('Hello World from Brython')
    # Insert Header into document body
    document <= html.H1("HELLO FROM BRYTHON", Id="main")

if __name__ == '__main__':
    display()
from frontend.main import display

class BrythonTest:
    @location(reverse('backend:index'))
    def test_example(self):
        display()
        h = document.getElementById("main")
        assert h.text == "HELLO FROM BRYTHON"

Solution 2:

Setup a wait_for condition in your location decorator. The test runner will wait until the specified condition is met:

class BrythonTest:
    @location(reverse('backend:index'), wait_for=lambda: document.getElementById("main") is not None)
    def test_example(self):
        h = document.getElementById("main")
        assert h.text == "HELLO FROM BRYTHON"